PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.3
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.3
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 0.9.3, at includes/window-chrome.php

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