PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
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 0.8.8 All 35 releases
← All changes | includes/registries/native-windows.php +160 -7 1.1.1 → 1.1.11 View file →
@@ -99,8 +99,27 @@
99 99 * paints. Declaring it here is what
100 100 * keeps it off the boot critical
101 101 * path: it travels with the window
102 102 * it extends. Default empty.
103 + * @type string[] $styles Companion style handles injected on
104 + * the window's first open, after the
105 + * window's own `$style`, in the order
106 + * given — so at equal specificity a
107 + * companion's overrides win, the same
108 + * source-order contract an enqueue
109 + * dependency gives. The styles-side
110 + * mirror of `$scripts`: a stylesheet
111 + * that only paints surfaces inside
112 + * this window is dead weight on every
113 + * document that never shows it —
114 + * declared here it costs nothing at
115 + * boot and never reaches chromeless
116 + * iframes at all. Unlike `$style`
117 + * (injected when the window registers,
118 + * so mid-session activations paint),
119 + * companions wait for the first open;
120 + * the deferral is the point. Default
121 + * empty.
103 122 * @type bool $preload_script Load `$script` (and `$scripts`) at
104 123 * shell boot instead of on first
105 124 * open. Default false — a window's
106 125 * bundle is dead weight until the
@@ -125,8 +144,27 @@
125 144 * @type int $min_height Minimum height (px). Default 220.
126 145 * @type string $placement 'dock' | 'none'. Default 'dock'.
127 146 * 'none' skips the tile (plugin
128 147 * opens the window programmatically).
148 + * A PROPOSED default only: the user's
149 + * OpenStation Preferences → Navigation
150 + * pick wins, and so does a right-click
151 + * "Keep in dock".
152 + * @type string $admin 'site' | 'network' | 'any'. Default
153 + * 'site': offered on every site's
154 + * shell and never on the network
155 + * admin's, which is right for a window
156 + * that reads the current site's REST
157 + * API. 'network' is the network
158 + * admin's shell only; 'any' is both.
159 + * @type string $nav_kind 'app' | 'control'. Default 'app'.
160 + * What the window IS, which decides
161 + * where its launcher defaults to (apps
162 + * to the desktop, controls to the
163 + * dock) and which dock zone it sits
164 + * in. Plugins want 'app'; 'control'
165 + * is for OpenStation's own
166 + * affordances.
129 167 * @type int $dock_order Sort key among system tiles,
130 168 * ascending; ties keep registration
131 169 * order. Default 0, which places the
132 170 * tile ahead of the shell's own
@@ -202,8 +240,9 @@
202 240 'icon' => 'dashicons-admin-generic',
203 241 'template' => null,
204 242 'script' => '',
205 243 'scripts' => array(),
244 + 'styles' => array(),
206 245 'preload_script' => false,
207 246 // Optional WP style handle (registered with `wp_register_style()`).
208 247 // Resolved at payload-build time so the shell can lazy-inject a
209 248 // `<link rel="stylesheet">` when a peer plugin is activated
@@ -215,8 +254,10 @@
215 254 'height' => 400,
216 255 'min_width' => 280,
217 256 'min_height' => 220,
218 257 'placement' => 'dock',
258 + 'admin' => 'site',
259 + 'nav_kind' => 'app',
219 260 'dock_order' => 0,
220 261 'placeable' => false,
221 262 'capabilities' => array(),
222 263 'autofocus' => false,
@@ -221,11 +262,17 @@
221 262 'capabilities' => array(),
222 263 'autofocus' => false,
223 264 'main_tab_label' => '',
224 265 'main_tab_padding' => '',
266 + // Admin pages this window answers for, `array( id, page )` per
267 + // entry. See `App::menu()` and `openstation_apps_menu_pages()`.
268 + 'menu_pages' => array(),
225 269 'config' => array(),
226 270 );
227 271 $args = wp_parse_args( $args, $defaults );
272 + if ( ! in_array( $args['admin'], array( 'site', 'network', 'any' ), true ) ) {
273 + $args['admin'] = 'site';
274 + }
228 275
229 276 // Capability gate — ALL listed caps must match. Fail closed.
230 277 foreach ( (array) $args['capabilities'] as $cap ) {
231 278 if ( ! current_user_can( (string) $cap ) ) {
@@ -263,8 +310,17 @@
263 310 $placement = in_array( $args['placement'], array( 'dock', 'none' ), true )
264 311 ? $args['placement']
265 312 : 'dock';
266 313
314 + // What the window IS, which is what decides where its launcher
315 + // goes by default and which dock zone it sits in. `'app'` for an
316 + // installed app (the default, and what every plugin wants);
317 + // `'control'` for an OpenStation affordance — the Trash is the
318 + // only shipped one.
319 + $nav_kind = in_array( $args['nav_kind'], array( 'app', 'control' ), true )
320 + ? $args['nav_kind']
321 + : 'app';
322 +
267 323 $entry = array(
268 324 'id' => $id,
269 325 'title' => (string) $args['title'],
270 326 'icon' => (string) $args['icon'],
@@ -281,8 +337,19 @@
281 337 }
282 338 )
283 339 )
284 340 ),
341 + // Companion style handles, same dedupe/strip as `scripts`.
342 + 'styles' => array_values(
343 + array_unique(
344 + array_filter(
345 + array_map( 'strval', (array) $args['styles'] ),
346 + static function ( $handle ) {
347 + return '' !== $handle;
348 + }
349 + )
350 + )
351 + ),
285 352 'preload_script' => (bool) $args['preload_script'],
286 353 'style' => (string) $args['style'],
287 354 'width' => (int) $args['width'],
288 355 'height' => (int) $args['height'],
@@ -288,8 +355,11 @@
288 355 'height' => (int) $args['height'],
289 356 'min_width' => (int) $args['min_width'],
290 357 'min_height' => (int) $args['min_height'],
291 358 'placement' => $placement,
359 + 'nav_kind' => $nav_kind,
360 + // Which admin's shell offers it; see the `admin` arg.
361 + 'admin' => $args['admin'],
292 362 // Sort key among system tiles, ascending. `0` (the default)
293 363 // puts a plugin's tile ahead of the shell's own trailing
294 364 // cluster — Mio 10, Overview 20, System 30 — which is where a
295 365 // launcher belongs. Trash uses 40 to sit at the very end.
@@ -303,8 +373,12 @@
303 373 // Bundle-bound config delivered through the same path as
304 374 // `wp_localize_script` `extra['data']` — see the `config` doc
305 375 // in this function's `$args` block and `openstation_resolve_script_payload()`
306 376 // for how it lands on the wire.
377 + // Admin pages this window answers for, `array( id, page )` per
378 + // entry — dropped here once, which is why the shell saw an
379 + // empty list and claimed none of them.
380 + 'menu_pages' => is_array( $args['menu_pages'] ) ? array_values( $args['menu_pages'] ) : array(),
307 381 'config' => is_array( $args['config'] ) ? $args['config'] : array(),
308 382 );
309 383 openstation_native_window_registry( $id, $entry );
310 384
@@ -362,9 +436,9 @@
362 436 * Templates are inert until JS clones them out of the `<template>`
363 437 * tag — but Plugin Check still requires escape-on-output. The list
364 438 * extends `wp_kses_allowed_html( 'post' )` with form controls,
365 439 * `<os-*>` web components, and dashicon spans, plus permissive
366 - * `data-*`, `aria-*`, and component-specific attributes. Plugins
440 + * `data-*`, common ARIA, and component-specific attributes. Plugins
367 441 * registering their own native windows can extend the list via the
368 442 * `openstation_native_window_allowed_html` filter below.
369 443 *
370 444 * @return array<string,array<string,bool>>
@@ -386,9 +460,15 @@
386 460 'dir' => true,
387 461 'draggable' => true,
388 462 'contenteditable' => true,
389 463 'data-*' => true,
390 - 'aria-*' => true,
464 + // `wp_kses` only treats the `data-*` wildcard specially. ARIA
465 + // attributes must be admitted by their exact names or they are
466 + // silently stripped from native-window templates.
467 + 'aria-label' => true,
468 + 'aria-labelledby' => true,
469 + 'aria-current' => true,
470 + 'aria-hidden' => true,
391 471 // `full-width` is a layout-level flag honoured by
392 472 // `<os-form>` (and any future os-* container that opts in
393 473 // to row-spanning slotted children). Lives in the global
394 474 // allowlist so a plain `<div full-width>` wrapper isn't
@@ -457,8 +537,9 @@
457 537 'bordered' => true,
458 538 'compact' => true,
459 539 'loading' => true,
460 540 'loading-rows' => true,
541 + 'empty' => true,
461 542 'columns' => true,
462 543 'rows' => true,
463 544 'sortable' => true,
464 545 'expandable' => true,
@@ -463,8 +544,9 @@
463 544 'sortable' => true,
464 545 'expandable' => true,
465 546 'preset' => true,
466 547 'label' => true,
548 + 'heading' => true,
467 549 'description' => true,
468 550 'orientation' => true,
469 551 'level' => true,
470 552 'collapsed' => true,
@@ -652,9 +734,9 @@
652 734
653 735 $allowed = array_merge( $base, $extra );
654 736
655 737 // Promote the framework's global attrs (`slot`, `part`,
656 - // `full-width`, `data-*`, `aria-*`, …) to EVERY allowed tag —
738 + // `full-width`, `data-*`, common ARIA, …) to EVERY allowed tag —
657 739 // otherwise plain wrappers like `<div slot="header">` lose
658 740 // their `slot` attribute on the way through kses and get
659 741 // projected into the default slot instead of the named one.
660 742 // Caught by inspection when the Add User form's header
@@ -834,8 +916,53 @@
834 916 return $buffer;
835 917 }
836 918
837 919 /**
920 + * Run a native window's registered `config` through the
921 + * `openstation_native_window_config` filter, normalized to an array.
922 + *
923 + * Called at BOTH serialization points — the eager inline-script
924 + * attach in `openstation_enqueue_native_window_scripts()` and the
925 + * lazy `scriptL10n` synthesis in
926 + * `openstation_build_native_windows_payload()` — so the filter sees
927 + * every copy of the blob that can reach a browser.
928 + *
929 + * @param array $entry Registry entry (needs `id`; `config` optional).
930 + * @return array Filtered config. Empty array when nothing to ship.
931 + */
932 +function openstation_filter_native_window_config( $entry ) {
933 + $config = isset( $entry['config'] ) && is_array( $entry['config'] )
934 + ? $entry['config']
935 + : array();
936 +
937 + /**
938 + * Filter a native window's config blob at emit time.
939 + *
940 + * The registry snapshots `config` when `openstation_register_window()`
941 + * runs — usually `init`. This filter runs when the blob is
942 + * serialized for the browser (enqueue time on the eager path,
943 + * payload-build time on the lazy path), so values that depend on
944 + * hooks registered later in the bootstrap can be refreshed without
945 + * moving the whole registration. The WP Explorer uses it to
946 + * re-collect `previewActions` so plugins may add
947 + * `openstation_my_wordpress_preview_actions` callbacks any time
948 + * during a normal bootstrap, not just before `init` 99.
949 + *
950 + * Runs per request, after the current user is determined —
951 + * capability-gated values are safe to compute here.
952 + *
953 + * **Status: Experimental**
954 + *
955 + * @param array $config Config blob as registered (empty array
956 + * when the window registered none).
957 + * @param string $window_id Native window id.
958 + */
959 + $config = apply_filters( 'openstation_native_window_config', $config, (string) $entry['id'] );
960 +
961 + return is_array( $config ) ? $config : array();
962 +}
963 +
964 +/**
838 965 * Attach every registered native window's script data, and enqueue
839 966 * the handful of bundles that asked to load at boot.
840 967 *
841 968 * **A native window's bundle is not enqueued here.** It loads the
@@ -857,9 +984,9 @@
857 984 * builds that payload at 10, and data attached after it would ship a
858 985 * bundle with no config.
859 986 */
860 987 function openstation_enqueue_native_window_scripts() {
861 - if ( ! openstation_is_enabled() || openstation_is_chromeless_request() || openstation_is_classic_request() ) {
988 + if ( ! openstation_is_shell_request() ) {
862 989 return;
863 990 }
864 991 $registry = openstation_native_window_registry();
865 992 if ( ! is_array( $registry ) ) {
@@ -887,8 +1014,16 @@
887 1014 wp_enqueue_script( $entry['script'] );
888 1015 foreach ( (array) $entry['scripts'] as $companion ) {
889 1016 wp_enqueue_script( $companion );
890 1017 }
1018 + // Preload means "everything at boot" — companion styles
1019 + // ride along so the window paints styled on a preloaded
1020 + // first open, same as its scripts are already parsed.
1021 + if ( ! empty( $entry['styles'] ) ) {
1022 + foreach ( (array) $entry['styles'] as $companion_style ) {
1023 + wp_enqueue_style( $companion_style );
1024 + }
1025 + }
891 1026 }
892 1027 // Localize the config the JS side reads to register itself.
893 1028 wp_localize_script(
894 1029 $entry['script'],
@@ -926,15 +1061,16 @@
926 1061 // would mean a shell page shipped the identical assignment
927 1062 // twice: once as `before`, once as `l10n`. The bundle reads it
928 1063 // via `wp.os.getWindowConfig( id )` or directly at
929 1064 // `window.openStationWindowConfig[ id ]`.
930 - if ( $preload && ! empty( $entry['config'] ) && is_array( $entry['config'] ) ) {
1065 + $config = openstation_filter_native_window_config( $entry );
1066 + if ( $preload && ! empty( $config ) ) {
931 1067 wp_add_inline_script(
932 1068 $entry['script'],
933 1069 sprintf(
934 1070 'window.openStationWindowConfig=window.openStationWindowConfig||{};window.openStationWindowConfig[%s]=%s;',
935 1071 wp_json_encode( $entry['id'] ),
936 - wp_json_encode( $entry['config'] )
1072 + wp_json_encode( $config )
937 1073 ),
938 1074 'before'
939 1075 );
940 1076 }
@@ -948,9 +1084,9 @@
948 1084 * these via `document.getElementById( `os-native-window-${id}` )`
949 1085 * and clones them into each opened window's body.
950 1086 */
951 1087 function openstation_render_native_window_templates() {
952 - if ( ! openstation_is_enabled() || openstation_is_chromeless_request() || openstation_is_classic_request() ) {
1088 + if ( ! openstation_is_shell_request() ) {
953 1089 return;
954 1090 }
955 1091 $registry = openstation_native_window_registry();
956 1092 if ( ! is_array( $registry ) ) {
@@ -976,4 +1112,21 @@
976 1112 echo '</template>';
977 1113 }
978 1114 }
979 1115 add_action( 'admin_footer', 'openstation_render_native_window_templates', 20 );
1116 +
1117 +/**
1118 + * Whether a registered window is offered on the admin this request is
1119 + * in: the network admin's shell offers `network` and `any` windows,
1120 + * every site's shell offers `site` and `any`. See the `admin` arg of
1121 + * {@see openstation_register_window()}.
1122 + *
1123 + * @param array<string,mixed> $entry Registry entry.
1124 + * @return bool
1125 + */
1126 +function openstation_native_window_offered_here( $entry ) {
1127 + $admin = isset( $entry['admin'] ) ? (string) $entry['admin'] : 'site';
1128 + if ( 'any' === $admin ) {
1129 + return true;
1130 + }
1131 + return is_network_admin() ? 'network' === $admin : 'site' === $admin;
1132 +}