PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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 0.8.7 All 34 releases
desktop-mode / includes / window-chrome.php

window-chrome.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.10, at includes/window-chrome.php

971 lines 27.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Window-chrome customization framework — server-side registration APIs.
4 *
5 * Mirrors the commands / settings-tabs / title-bar-buttons registration
6 * pattern across four surfaces:
7 *
8 * - **Themes** (Layer 1) — per-window CSS-variable maps.
9 * `openstation_register_window_theme_script()` /
10 * `openstation_register_window_theme()`.
11 *
12 * - **Controls** (Layer 2) — title-bar buttons (close / minimize /
13 * maximize plus plugin custom controls).
14 * `openstation_register_window_control_script()` /
15 * `openstation_register_window_control()`.
16 *
17 * - **Slots** (Layer 3) — named title-bar regions plugins can
18 * replace (icon, title, before-controls, …).
19 * `openstation_register_window_slot_script()` /
20 * `openstation_register_window_slot()`.
21 *
22 * - **Chrome** (Layer 4, Experimental) — full title-bar render
23 * replacement.
24 * `openstation_register_window_chrome_script()` /
25 * `openstation_register_window_chrome()`.
26 *
27 * Each surface contributes a `serverWindow*Scripts` and (optionally)
28 * `serverWindow*` array to the shell payload, consumed by the matching
29 * sync module under `src/window-chrome/{themes,controls,slots,chrome}/server-sync.ts`.
30 *
31 * @package OpenStation
32 */
33
34 defined( 'ABSPATH' ) || exit;
35
36 /*
37 ============================================================
38 * Layer 1 — Themes
39 * ============================================================
40 */
41
42 /**
43 * Declare a WP-registered script handle as a window-theme provider.
44 *
45 * The script's JS calls `wp.os.registerWindowTheme( { id, tokens,
46 * match, owner } )` as usual. Plugins that pass `owner` matching this
47 * handle get live unregister on deactivation.
48 *
49 * @param string $handle WP-registered script handle.
50 * @return true|WP_Error `true` on success; `WP_Error` on validation failure.
51 */
52 function openstation_register_window_theme_script( $handle ) {
53 $handle = (string) $handle;
54 if ( '' === $handle ) {
55 return openstation_registration_error(
56 'openstation_missing_handle',
57 __( 'Window theme script registration requires a non-empty script handle.', 'desktop-mode' )
58 );
59 }
60
61 openstation_window_theme_script_registry( $handle, true );
62
63 /**
64 * Fires after a desktop window-theme script handle is registered.
65 *
66 * @param string $handle The registered script handle.
67 */
68 do_action( 'openstation_window_theme_script_registered', $handle );
69
70 return true;
71 }
72
73 /**
74 * Declare a window theme server-side. Optional companion to
75 * `openstation_register_window_theme_script()` for plugins that want
76 * to ship a tokens map without writing JS — designers can hand off
77 * a single PHP-array of CSS variables and call it done.
78 *
79 * @param array $args {
80 * @type string $id Unique theme id (`vendor/sub-id`). Required.
81 * @type string $label Human-readable label. Default empty.
82 * @type array $tokens CSS-variable map (keys must start with `--`). Required.
83 * @type int $priority Override priority (higher wins). Default 100.
84 * @type string $script Optional script handle (also registers it).
85 * }
86 * @return true|WP_Error
87 */
88 function openstation_register_window_theme( $args = array() ) {
89 $defaults = array(
90 'id' => '',
91 'label' => '',
92 'tokens' => array(),
93 'priority' => 100,
94 'script' => '',
95 );
96 $args = wp_parse_args( $args, $defaults );
97
98 $id = (string) $args['id'];
99 if ( '' === $id ) {
100 return openstation_registration_error(
101 'openstation_missing_id',
102 __( 'Window theme registration requires a non-empty `id`.', 'desktop-mode' )
103 );
104 }
105 if ( ! is_array( $args['tokens'] ) || empty( $args['tokens'] ) ) {
106 return openstation_registration_error(
107 'openstation_missing_tokens',
108 __( 'Window theme registration requires a non-empty `tokens` map.', 'desktop-mode' ),
109 array( 'id' => $id )
110 );
111 }
112 $tokens = array();
113 foreach ( $args['tokens'] as $key => $value ) {
114 $key = (string) $key;
115 if ( '' === $key || 0 !== strpos( $key, '--' ) ) {
116 return openstation_registration_error(
117 'openstation_invalid_token',
118 __( 'Window theme tokens must use CSS custom-property keys (start with "--").', 'desktop-mode' ),
119 array(
120 'id' => $id,
121 'key' => $key,
122 )
123 );
124 }
125 $tokens[ $key ] = (string) $value;
126 }
127
128 $entry = array(
129 'id' => $id,
130 'label' => (string) $args['label'],
131 'tokens' => $tokens,
132 'priority' => (int) $args['priority'],
133 'script' => (string) $args['script'],
134 );
135 openstation_window_theme_registry( $id, $entry );
136
137 if ( '' !== $entry['script'] ) {
138 openstation_window_theme_script_registry( $entry['script'], true );
139 }
140
141 /**
142 * Fires after a desktop window-theme is successfully registered.
143 *
144 * @param string $id The theme id.
145 * @param array $entry The stored registry entry.
146 */
147 do_action( 'openstation_window_theme_registered', $id, $entry );
148
149 return true;
150 }
151
152 /**
153 * Internal module-level registry for theme script handles.
154 *
155 * @internal
156 *
157 * @param string $handle Script handle to read or write.
158 * @param bool|null $value Pass `true` to register; `null` to read only.
159 * @return array|bool When called with no args returns the full store.
160 */
161 function openstation_window_theme_script_registry( $handle = '', $value = null ) {
162 static $store = array();
163
164 if ( '__flush__' === (string) $handle ) {
165 $store = array();
166 return array();
167 }
168 if ( '' === (string) $handle ) {
169 return $store;
170 }
171 if ( null !== $value ) {
172 $store[ (string) $handle ] = (bool) $value;
173 }
174 return isset( $store[ (string) $handle ] ) ? $store[ (string) $handle ] : false;
175 }
176
177 /** Flush the theme-script registry. Tests only. */
178 function openstation_flush_window_theme_script_registry() {
179 openstation_window_theme_script_registry( '__flush__' );
180 }
181
182 /**
183 * Internal module-level registry for window themes.
184 *
185 * @internal
186 *
187 * @param string $id Theme id to read or write.
188 * @param array|null $entry Entry to store, or `null` to read.
189 * @return array|null
190 */
191 function openstation_window_theme_registry( $id = '', $entry = null ) {
192 static $store = array();
193
194 if ( '__flush__' === (string) $id ) {
195 $store = array();
196 return array();
197 }
198 if ( '' === (string) $id ) {
199 return $store;
200 }
201 if ( null !== $entry ) {
202 $store[ (string) $id ] = $entry;
203 }
204 return isset( $store[ (string) $id ] ) ? $store[ (string) $id ] : null;
205 }
206
207 /** Flush the theme registry. Tests only. */
208 function openstation_flush_window_theme_registry() {
209 openstation_window_theme_registry( '__flush__' );
210 }
211
212 /**
213 * Build the theme-script payload. Same shape as
214 * `openstation_build_desktop_command_scripts_payload()`.
215 *
216 * @return array[] List of `{ handle, scriptUrl }` entries.
217 */
218 function openstation_build_window_theme_scripts_payload() {
219 $registry = openstation_window_theme_script_registry();
220 if ( ! is_array( $registry ) || empty( $registry ) ) {
221 return array();
222 }
223
224 $out = array();
225 $seen = array();
226 foreach ( $registry as $handle => $active ) {
227 if ( ! $active || isset( $seen[ $handle ] ) ) {
228 continue;
229 }
230 $payload = openstation_resolve_script_payload( $handle );
231 if ( '' === $payload['url'] ) {
232 openstation_warn_unresolvable_script_handle(
233 'openstation_register_window_theme_script',
234 'Window theme',
235 (string) $handle
236 );
237 continue;
238 }
239 $out[] = array(
240 'handle' => (string) $handle,
241 'scriptUrl' => $payload['url'],
242 'scriptBefore' => $payload['before'],
243 'scriptAfter' => $payload['after'],
244 'scriptL10n' => $payload['l10n'],
245 'scriptTranslations' => $payload['translations'],
246 // The handle's dependency closure, replayed before the bundle
247 // on its lazy load — see `openstation_resolve_script_dependencies()`.
248 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
249 );
250 $seen[ $handle ] = true;
251 }
252 return $out;
253 }
254
255 /**
256 * Build the theme-metadata payload. Resolves the optional companion
257 * script handle for each entry.
258 *
259 * @return array[]
260 */
261 function openstation_build_window_themes_payload() {
262 $registry = openstation_window_theme_registry();
263 if ( ! is_array( $registry ) || empty( $registry ) ) {
264 return array();
265 }
266
267 $out = array();
268 foreach ( $registry as $entry ) {
269 $handle = (string) $entry['script'];
270 $payload = '' !== $handle
271 ? openstation_resolve_script_payload( $handle )
272 : array(
273 'url' => '',
274 'before' => array(),
275 'after' => array(),
276 'l10n' => array(),
277 'translations' => '',
278 );
279 $out[] = array(
280 'id' => (string) $entry['id'],
281 'label' => (string) $entry['label'],
282 'tokens' => (array) $entry['tokens'],
283 'priority' => (int) $entry['priority'],
284 'scriptUrl' => $payload['url'],
285 'scriptHandle' => $handle,
286 'scriptBefore' => $payload['before'],
287 'scriptAfter' => $payload['after'],
288 'scriptL10n' => $payload['l10n'],
289 'scriptTranslations' => $payload['translations'],
290 // The handle's dependency closure, replayed before the bundle
291 // on its lazy load — see `openstation_resolve_script_dependencies()`.
292 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
293 );
294 }
295 return $out;
296 }
297
298 /*
299 ============================================================
300 * Layer 2 — Controls
301 * ============================================================
302 */
303
304 /**
305 * Declare a WP-registered script handle as a window-control provider.
306 *
307 * @param string $handle WP-registered script handle.
308 * @return true|WP_Error
309 */
310 function openstation_register_window_control_script( $handle ) {
311 $handle = (string) $handle;
312 if ( '' === $handle ) {
313 return openstation_registration_error(
314 'openstation_missing_handle',
315 __( 'Window control script registration requires a non-empty script handle.', 'desktop-mode' )
316 );
317 }
318
319 openstation_window_control_script_registry( $handle, true );
320
321 /**
322 * Fires after a window-control script handle is registered.
323 *
324 * @param string $handle The registered script handle.
325 */
326 do_action( 'openstation_window_control_script_registered', $handle );
327
328 return true;
329 }
330
331 /**
332 * Declare a window control server-side. The control's `onClick` /
333 * `render` callback always lives JS-side.
334 *
335 * @param array $args {
336 * @type string $id Required, `vendor/sub-id`.
337 * @type string $label Required.
338 * @type string $icon Dashicons class / built-in key / SVG.
339 * @type string $placement `'left' | 'right' | 'controls'`. Default `'left'`.
340 * @type int $order Sort within placement. Default 100.
341 * @type string $script Optional companion script handle.
342 * }
343 * @return true|WP_Error
344 */
345 function openstation_register_window_control( $args = array() ) {
346 $defaults = array(
347 'id' => '',
348 'label' => '',
349 'icon' => '',
350 'placement' => 'left',
351 'order' => 100,
352 'script' => '',
353 );
354 $args = wp_parse_args( $args, $defaults );
355
356 $id = (string) $args['id'];
357 if ( '' === $id ) {
358 return openstation_registration_error(
359 'openstation_missing_id',
360 __( 'Window control registration requires a non-empty `id`.', 'desktop-mode' )
361 );
362 }
363 if ( '' === (string) $args['label'] ) {
364 return openstation_registration_error(
365 'openstation_missing_label',
366 __( 'Window control registration requires a non-empty `label`.', 'desktop-mode' ),
367 array( 'id' => $id )
368 );
369 }
370 $placement = (string) $args['placement'];
371 if ( ! in_array( $placement, array( 'left', 'right', 'controls' ), true ) ) {
372 return openstation_registration_error(
373 'openstation_invalid_placement',
374 __( 'Window control `placement` must be one of "left", "right", "controls".', 'desktop-mode' ),
375 array(
376 'id' => $id,
377 'placement' => $placement,
378 )
379 );
380 }
381
382 $entry = array(
383 'id' => $id,
384 'label' => (string) $args['label'],
385 'icon' => (string) $args['icon'],
386 'placement' => $placement,
387 'order' => (int) $args['order'],
388 'script' => (string) $args['script'],
389 );
390 openstation_window_control_registry( $id, $entry );
391
392 if ( '' !== $entry['script'] ) {
393 openstation_window_control_script_registry( $entry['script'], true );
394 }
395
396 /**
397 * Fires after a window-control is successfully registered.
398 *
399 * @param string $id The control id.
400 * @param array $entry The stored registry entry.
401 */
402 do_action( 'openstation_window_control_registered', $id, $entry );
403
404 return true;
405 }
406
407 /** @internal */
408 function openstation_window_control_script_registry( $handle = '', $value = null ) {
409 static $store = array();
410
411 if ( '__flush__' === (string) $handle ) {
412 $store = array();
413 return array();
414 }
415 if ( '' === (string) $handle ) {
416 return $store;
417 }
418 if ( null !== $value ) {
419 $store[ (string) $handle ] = (bool) $value;
420 }
421 return isset( $store[ (string) $handle ] ) ? $store[ (string) $handle ] : false;
422 }
423
424 /** Tests only. */
425 function openstation_flush_window_control_script_registry() {
426 openstation_window_control_script_registry( '__flush__' );
427 }
428
429 /** @internal */
430 function openstation_window_control_registry( $id = '', $entry = null ) {
431 static $store = array();
432
433 if ( '__flush__' === (string) $id ) {
434 $store = array();
435 return array();
436 }
437 if ( '' === (string) $id ) {
438 return $store;
439 }
440 if ( null !== $entry ) {
441 $store[ (string) $id ] = $entry;
442 }
443 return isset( $store[ (string) $id ] ) ? $store[ (string) $id ] : null;
444 }
445
446 /** Tests only. */
447 function openstation_flush_window_control_registry() {
448 openstation_window_control_registry( '__flush__' );
449 }
450
451 /**
452 * @return array[] List of `{ handle, scriptUrl }` entries.
453 */
454 function openstation_build_window_control_scripts_payload() {
455 $registry = openstation_window_control_script_registry();
456 if ( ! is_array( $registry ) || empty( $registry ) ) {
457 return array();
458 }
459
460 $out = array();
461 $seen = array();
462 foreach ( $registry as $handle => $active ) {
463 if ( ! $active || isset( $seen[ $handle ] ) ) {
464 continue;
465 }
466 $payload = openstation_resolve_script_payload( $handle );
467 if ( '' === $payload['url'] ) {
468 openstation_warn_unresolvable_script_handle(
469 'openstation_register_window_control_script',
470 'Window control',
471 (string) $handle
472 );
473 continue;
474 }
475 $out[] = array(
476 'handle' => (string) $handle,
477 'scriptUrl' => $payload['url'],
478 'scriptBefore' => $payload['before'],
479 'scriptAfter' => $payload['after'],
480 'scriptL10n' => $payload['l10n'],
481 'scriptTranslations' => $payload['translations'],
482 // The handle's dependency closure, replayed before the bundle
483 // on its lazy load — see `openstation_resolve_script_dependencies()`.
484 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
485 );
486 $seen[ $handle ] = true;
487 }
488 return $out;
489 }
490
491 /**
492 * @return array[]
493 */
494 function openstation_build_window_controls_payload() {
495 $registry = openstation_window_control_registry();
496 if ( ! is_array( $registry ) || empty( $registry ) ) {
497 return array();
498 }
499
500 $out = array();
501 foreach ( $registry as $entry ) {
502 $handle = (string) $entry['script'];
503 $payload = '' !== $handle
504 ? openstation_resolve_script_payload( $handle )
505 : array(
506 'url' => '',
507 'before' => array(),
508 'after' => array(),
509 'l10n' => array(),
510 'translations' => '',
511 );
512 $out[] = array(
513 'id' => (string) $entry['id'],
514 'label' => (string) $entry['label'],
515 'icon' => (string) $entry['icon'],
516 'placement' => (string) $entry['placement'],
517 'order' => (int) $entry['order'],
518 'scriptUrl' => $payload['url'],
519 'scriptHandle' => $handle,
520 'scriptBefore' => $payload['before'],
521 'scriptAfter' => $payload['after'],
522 'scriptL10n' => $payload['l10n'],
523 'scriptTranslations' => $payload['translations'],
524 // The handle's dependency closure, replayed before the bundle
525 // on its lazy load — see `openstation_resolve_script_dependencies()`.
526 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
527 );
528 }
529 return $out;
530 }
531
532 /*
533 ============================================================
534 * Layer 3 — Slots
535 * ============================================================
536 */
537
538 /**
539 * Canonical slot names. Mirrors the `WindowSlotName` TypeScript
540 * union in `src/types.ts`.
541 *
542 * @return string[]
543 */
544 function openstation_window_slot_names() {
545 return array(
546 'before-titlebar',
547 'before-icon',
548 'icon',
549 'title',
550 'after-title',
551 'before-controls',
552 'controls',
553 'after-controls',
554 'after-titlebar',
555 );
556 }
557
558 /**
559 * @param string $handle WP-registered script handle.
560 * @return true|WP_Error
561 */
562 function openstation_register_window_slot_script( $handle ) {
563 $handle = (string) $handle;
564 if ( '' === $handle ) {
565 return openstation_registration_error(
566 'openstation_missing_handle',
567 __( 'Window slot script registration requires a non-empty script handle.', 'desktop-mode' )
568 );
569 }
570
571 openstation_window_slot_script_registry( $handle, true );
572
573 /**
574 * Fires after a window-slot script handle is registered.
575 *
576 * @param string $handle The registered script handle.
577 */
578 do_action( 'openstation_window_slot_script_registered', $handle );
579
580 return true;
581 }
582
583 /**
584 * @param array $args {
585 * @type string $id Required.
586 * @type string $slot Required, one of {@see openstation_window_slot_names()}.
587 * @type int $order Default 100.
588 * @type string $script Optional script handle.
589 * }
590 * @return true|WP_Error
591 */
592 function openstation_register_window_slot( $args = array() ) {
593 $defaults = array(
594 'id' => '',
595 'slot' => '',
596 'order' => 100,
597 'script' => '',
598 );
599 $args = wp_parse_args( $args, $defaults );
600
601 $id = (string) $args['id'];
602 if ( '' === $id ) {
603 return openstation_registration_error(
604 'openstation_missing_id',
605 __( 'Window slot registration requires a non-empty `id`.', 'desktop-mode' )
606 );
607 }
608 $slot = (string) $args['slot'];
609 if ( ! in_array( $slot, openstation_window_slot_names(), true ) ) {
610 return openstation_registration_error(
611 'openstation_invalid_slot',
612 __( 'Window slot registration requires a known `slot` name.', 'desktop-mode' ),
613 array(
614 'id' => $id,
615 'slot' => $slot,
616 )
617 );
618 }
619
620 $entry = array(
621 'id' => $id,
622 'slot' => $slot,
623 'order' => (int) $args['order'],
624 'script' => (string) $args['script'],
625 );
626 openstation_window_slot_registry( $id, $entry );
627
628 if ( '' !== $entry['script'] ) {
629 openstation_window_slot_script_registry( $entry['script'], true );
630 }
631
632 /**
633 * Fires after a window-slot is successfully registered.
634 *
635 * @param string $id The slot-renderer id.
636 * @param array $entry The stored registry entry.
637 */
638 do_action( 'openstation_window_slot_registered', $id, $entry );
639
640 return true;
641 }
642
643 /** @internal */
644 function openstation_window_slot_script_registry( $handle = '', $value = null ) {
645 static $store = array();
646
647 if ( '__flush__' === (string) $handle ) {
648 $store = array();
649 return array();
650 }
651 if ( '' === (string) $handle ) {
652 return $store;
653 }
654 if ( null !== $value ) {
655 $store[ (string) $handle ] = (bool) $value;
656 }
657 return isset( $store[ (string) $handle ] ) ? $store[ (string) $handle ] : false;
658 }
659
660 /** Tests only. */
661 function openstation_flush_window_slot_script_registry() {
662 openstation_window_slot_script_registry( '__flush__' );
663 }
664
665 /** @internal */
666 function openstation_window_slot_registry( $id = '', $entry = null ) {
667 static $store = array();
668
669 if ( '__flush__' === (string) $id ) {
670 $store = array();
671 return array();
672 }
673 if ( '' === (string) $id ) {
674 return $store;
675 }
676 if ( null !== $entry ) {
677 $store[ (string) $id ] = $entry;
678 }
679 return isset( $store[ (string) $id ] ) ? $store[ (string) $id ] : null;
680 }
681
682 /** Tests only. */
683 function openstation_flush_window_slot_registry() {
684 openstation_window_slot_registry( '__flush__' );
685 }
686
687 /**
688 * @return array[] List of `{ handle, scriptUrl }` entries.
689 */
690 function openstation_build_window_slot_scripts_payload() {
691 $registry = openstation_window_slot_script_registry();
692 if ( ! is_array( $registry ) || empty( $registry ) ) {
693 return array();
694 }
695
696 $out = array();
697 $seen = array();
698 foreach ( $registry as $handle => $active ) {
699 if ( ! $active || isset( $seen[ $handle ] ) ) {
700 continue;
701 }
702 $payload = openstation_resolve_script_payload( $handle );
703 if ( '' === $payload['url'] ) {
704 openstation_warn_unresolvable_script_handle(
705 'openstation_register_window_slot_script',
706 'Window slot',
707 (string) $handle
708 );
709 continue;
710 }
711 $out[] = array(
712 'handle' => (string) $handle,
713 'scriptUrl' => $payload['url'],
714 'scriptBefore' => $payload['before'],
715 'scriptAfter' => $payload['after'],
716 'scriptL10n' => $payload['l10n'],
717 'scriptTranslations' => $payload['translations'],
718 // The handle's dependency closure, replayed before the bundle
719 // on its lazy load — see `openstation_resolve_script_dependencies()`.
720 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
721 );
722 $seen[ $handle ] = true;
723 }
724 return $out;
725 }
726
727 /**
728 * @return array[]
729 */
730 function openstation_build_window_slots_payload() {
731 $registry = openstation_window_slot_registry();
732 if ( ! is_array( $registry ) || empty( $registry ) ) {
733 return array();
734 }
735
736 $out = array();
737 foreach ( $registry as $entry ) {
738 $handle = (string) $entry['script'];
739 $payload = '' !== $handle
740 ? openstation_resolve_script_payload( $handle )
741 : array(
742 'url' => '',
743 'before' => array(),
744 'after' => array(),
745 'l10n' => array(),
746 'translations' => '',
747 );
748 $out[] = array(
749 'id' => (string) $entry['id'],
750 'slot' => (string) $entry['slot'],
751 'order' => (int) $entry['order'],
752 'scriptUrl' => $payload['url'],
753 'scriptHandle' => $handle,
754 'scriptBefore' => $payload['before'],
755 'scriptAfter' => $payload['after'],
756 'scriptL10n' => $payload['l10n'],
757 'scriptTranslations' => $payload['translations'],
758 // The handle's dependency closure, replayed before the bundle
759 // on its lazy load — see `openstation_resolve_script_dependencies()`.
760 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
761 );
762 }
763 return $out;
764 }
765
766 /*
767 ============================================================
768 * Layer 4 — Custom chrome (Experimental)
769 * ============================================================
770 */
771
772 /**
773 * Declare a WP-registered script handle as a window-chrome provider.
774 *
775 * **Experimental** — chrome render contract may change.
776 *
777 * @param string $handle WP-registered script handle.
778 * @return true|WP_Error
779 */
780 function openstation_register_window_chrome_script( $handle ) {
781 $handle = (string) $handle;
782 if ( '' === $handle ) {
783 return openstation_registration_error(
784 'openstation_missing_handle',
785 __( 'Window chrome script registration requires a non-empty script handle.', 'desktop-mode' )
786 );
787 }
788
789 openstation_window_chrome_script_registry( $handle, true );
790
791 /**
792 * Fires after a window-chrome script handle is registered.
793 *
794 * @param string $handle The registered script handle.
795 */
796 do_action( 'openstation_window_chrome_script_registered', $handle );
797
798 return true;
799 }
800
801 /**
802 * Declare a custom chrome server-side. **Experimental** — chrome
803 * render contract may change.
804 *
805 * @param array $args {
806 * @type string $id Required.
807 * @type string $label Default empty.
808 * @type string $script Optional script handle.
809 * }
810 * @return true|WP_Error
811 */
812 function openstation_register_window_chrome( $args = array() ) {
813 $defaults = array(
814 'id' => '',
815 'label' => '',
816 'script' => '',
817 );
818 $args = wp_parse_args( $args, $defaults );
819
820 $id = (string) $args['id'];
821 if ( '' === $id ) {
822 return openstation_registration_error(
823 'openstation_missing_id',
824 __( 'Window chrome registration requires a non-empty `id`.', 'desktop-mode' )
825 );
826 }
827
828 $entry = array(
829 'id' => $id,
830 'label' => (string) $args['label'],
831 'script' => (string) $args['script'],
832 );
833 openstation_window_chrome_registry( $id, $entry );
834
835 if ( '' !== $entry['script'] ) {
836 openstation_window_chrome_script_registry( $entry['script'], true );
837 }
838
839 /**
840 * Fires after a window-chrome is successfully registered.
841 *
842 * @param string $id The chrome id.
843 * @param array $entry The stored registry entry.
844 */
845 do_action( 'openstation_window_chrome_registered', $id, $entry );
846
847 return true;
848 }
849
850 /** @internal */
851 function openstation_window_chrome_script_registry( $handle = '', $value = null ) {
852 static $store = array();
853
854 if ( '__flush__' === (string) $handle ) {
855 $store = array();
856 return array();
857 }
858 if ( '' === (string) $handle ) {
859 return $store;
860 }
861 if ( null !== $value ) {
862 $store[ (string) $handle ] = (bool) $value;
863 }
864 return isset( $store[ (string) $handle ] ) ? $store[ (string) $handle ] : false;
865 }
866
867 /** Tests only. */
868 function openstation_flush_window_chrome_script_registry() {
869 openstation_window_chrome_script_registry( '__flush__' );
870 }
871
872 /** @internal */
873 function openstation_window_chrome_registry( $id = '', $entry = null ) {
874 static $store = array();
875
876 if ( '__flush__' === (string) $id ) {
877 $store = array();
878 return array();
879 }
880 if ( '' === (string) $id ) {
881 return $store;
882 }
883 if ( null !== $entry ) {
884 $store[ (string) $id ] = $entry;
885 }
886 return isset( $store[ (string) $id ] ) ? $store[ (string) $id ] : null;
887 }
888
889 /** Tests only. */
890 function openstation_flush_window_chrome_registry() {
891 openstation_window_chrome_registry( '__flush__' );
892 }
893
894 /**
895 * @return array[] List of `{ handle, scriptUrl }` entries.
896 */
897 function openstation_build_window_chrome_scripts_payload() {
898 $registry = openstation_window_chrome_script_registry();
899 if ( ! is_array( $registry ) || empty( $registry ) ) {
900 return array();
901 }
902
903 $out = array();
904 $seen = array();
905 foreach ( $registry as $handle => $active ) {
906 if ( ! $active || isset( $seen[ $handle ] ) ) {
907 continue;
908 }
909 $payload = openstation_resolve_script_payload( $handle );
910 if ( '' === $payload['url'] ) {
911 openstation_warn_unresolvable_script_handle(
912 'openstation_register_window_chrome_script',
913 'Window chrome',
914 (string) $handle
915 );
916 continue;
917 }
918 $out[] = array(
919 'handle' => (string) $handle,
920 'scriptUrl' => $payload['url'],
921 'scriptBefore' => $payload['before'],
922 'scriptAfter' => $payload['after'],
923 'scriptL10n' => $payload['l10n'],
924 'scriptTranslations' => $payload['translations'],
925 // The handle's dependency closure, replayed before the bundle
926 // on its lazy load — see `openstation_resolve_script_dependencies()`.
927 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
928 );
929 $seen[ $handle ] = true;
930 }
931 return $out;
932 }
933
934 /**
935 * @return array[]
936 */
937 function openstation_build_window_chromes_payload() {
938 $registry = openstation_window_chrome_registry();
939 if ( ! is_array( $registry ) || empty( $registry ) ) {
940 return array();
941 }
942
943 $out = array();
944 foreach ( $registry as $entry ) {
945 $handle = (string) $entry['script'];
946 $payload = '' !== $handle
947 ? openstation_resolve_script_payload( $handle )
948 : array(
949 'url' => '',
950 'before' => array(),
951 'after' => array(),
952 'l10n' => array(),
953 'translations' => '',
954 );
955 $out[] = array(
956 'id' => (string) $entry['id'],
957 'label' => (string) $entry['label'],
958 'scriptUrl' => $payload['url'],
959 'scriptHandle' => $handle,
960 'scriptBefore' => $payload['before'],
961 'scriptAfter' => $payload['after'],
962 'scriptL10n' => $payload['l10n'],
963 'scriptTranslations' => $payload['translations'],
964 // The handle's dependency closure, replayed before the bundle
965 // on its lazy load — see `openstation_resolve_script_dependencies()`.
966 'scriptDeps' => openstation_resolve_script_dependencies( $handle ),
967 );
968 }
969 return $out;
970 }
971