PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.9
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.9
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 0.8.6 All 33 releases
desktop-mode / includes / registries / widgets.php

widgets.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.9, at includes/registries/widgets.php

258 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Widgets registry.
4 *
5 * Server-side registration API + payload builder + asset enqueue
6 * for the right-column widget layer. Plugin-side JS publishes
7 * the full widget def on `window.openStationWidgets[ id ]`; this
8 * module is the PHP side that announces them and ships their
9 * script handles into the boot payload.
10 *
11 * Extracted from `components.php` during the architecture-0.8.1
12 * PHP slicing (phase 6).
13 *
14 * @package OpenStation
15 */
16
17 defined( 'ABSPATH' ) || exit;
18
19 /**
20 * Register a server-side desktop widget. Symmetric to
21 * {@see openstation_register_window()} for the right-column widget
22 * layer: plugin declares the widget's metadata + script handle in
23 * PHP; shell syncs its registry from the live payload so
24 * activation / deactivation map to picker add / remove without a
25 * browser reload.
26 *
27 * The mount callback still lives in JS — not serializable across
28 * the wire. Plugins register it on
29 * `window.openStationWidgets[ <id> ]` as a `(container, ctx) =>
30 * teardown` function. The shell reads that global once the
31 * declared script loads and wraps it into a WidgetDef.
32 *
33 * Example:
34 *
35 * ```php
36 * openstation_register_widget( 'myplugin/stats', array(
37 * 'label' => __( 'Stats', 'my-plugin' ),
38 * 'description' => __( 'Live analytics rollup', 'my-plugin' ),
39 * 'icon' => 'dashicons-chart-bar',
40 * 'script' => 'my-plugin-desktop-widgets',
41 * 'movable' => true,
42 * 'resizable' => true,
43 * 'default_width' => 280,
44 * 'default_height' => 180,
45 * ) );
46 * ```
47 *
48 * ```js
49 * // Inside my-plugin-desktop-widgets.js:
50 * window.openStationWidgets = window.openStationWidgets || {};
51 * window.openStationWidgets[ 'myplugin/stats' ] = function ( container, ctx ) {
52 * container.append( buildDOM() );
53 * return function teardown() { };
54 * };
55 * ```
56 *
57 * @param string $id Widget id. Must match the key the JS side
58 * uses on `window.openStationWidgets[ … ]`.
59 * @param array $args {
60 * @type string $label Human-readable picker label. Required.
61 * @type string $description Picker subtitle. Default empty.
62 * @type string $icon Dashicons class for the picker.
63 * Default 'dashicons-admin-generic'.
64 * @type string $script Enqueued script handle that owns
65 * the mount callback. Optional — omit
66 * when the mount callback is declared
67 * by a script already on the shell
68 * page. Default empty.
69 * @type bool $movable Allow drag out of the right column.
70 * @type bool $resizable Allow user resize.
71 * @type int $min_width
72 * @type int $min_height
73 * @type int $max_width
74 * @type int $max_height
75 * @type int $default_width First-mount floating width.
76 * @type int $default_height First-mount floating height.
77 * @type string[] $capabilities Gate: ALL caps must match. Any
78 * missed cap returns
79 * `WP_Error openstation_capability_denied`.
80 * }
81 * @return true|WP_Error `true` on success; `WP_Error` otherwise.
82 */
83 function openstation_register_widget( $id, $args = array() ) {
84 $id = (string) $id;
85 if ( '' === $id ) {
86 return openstation_registration_error(
87 'openstation_missing_id',
88 __( 'Widget id is required.', 'desktop-mode' )
89 );
90 }
91
92 $defaults = array(
93 'label' => '',
94 'description' => '',
95 'icon' => 'dashicons-admin-generic',
96 'script' => '',
97 'movable' => false,
98 'resizable' => false,
99 'min_width' => 0,
100 'min_height' => 0,
101 'max_width' => 0,
102 'max_height' => 0,
103 'default_width' => 0,
104 'default_height' => 0,
105 'capabilities' => array(),
106 );
107 $args = wp_parse_args( $args, $defaults );
108
109 foreach ( (array) $args['capabilities'] as $cap ) {
110 if ( ! current_user_can( (string) $cap ) ) {
111 return openstation_registration_error(
112 'openstation_capability_denied',
113 sprintf(
114 /* translators: %s: capability slug. */
115 __( 'Current user lacks the %s capability required to register this widget.', 'desktop-mode' ),
116 (string) $cap
117 ),
118 array(
119 'capability' => (string) $cap,
120 'id' => $id,
121 )
122 );
123 }
124 }
125
126 // Required fields. The script handle isn't strictly required —
127 // a plugin could register a widget whose mount callback is
128 // declared on the shell page's own JS (edge case; still valid).
129 if ( '' === (string) $args['label'] ) {
130 return openstation_registration_error(
131 'openstation_missing_label',
132 __( 'Widget registration requires a non-empty `label`.', 'desktop-mode' ),
133 array( 'id' => $id )
134 );
135 }
136
137 $entry = array(
138 'id' => $id,
139 'label' => (string) $args['label'],
140 'description' => (string) $args['description'],
141 'icon' => (string) $args['icon'],
142 'script' => (string) $args['script'],
143 'movable' => (bool) $args['movable'],
144 'resizable' => (bool) $args['resizable'],
145 'min_width' => (int) $args['min_width'],
146 'min_height' => (int) $args['min_height'],
147 'max_width' => (int) $args['max_width'],
148 'max_height' => (int) $args['max_height'],
149 'default_width' => (int) $args['default_width'],
150 'default_height' => (int) $args['default_height'],
151 );
152 openstation_desktop_widget_registry( $id, $entry );
153
154 /**
155 * Fires after a desktop widget is successfully registered.
156 *
157 * Does NOT fire when `openstation_register_widget()` returns a
158 * `WP_Error`.
159 *
160 * @param string $id The widget id.
161 * @param array $entry The stored registry entry.
162 */
163 do_action( 'openstation_widget_registered', $id, $entry );
164
165 return true;
166 }
167
168 /**
169 * Internal module-level registry for widgets registered via
170 * {@see openstation_register_widget()}. Same pattern as
171 * {@see openstation_native_window_registry()}.
172 *
173 * @internal
174 */
175 function openstation_desktop_widget_registry( $id = '', $entry = null ) {
176 static $store = array();
177
178 if ( '' === (string) $id ) {
179 return $store;
180 }
181 if ( null !== $entry ) {
182 $store[ $id ] = $entry;
183 }
184 return isset( $store[ $id ] ) ? $store[ $id ] : null;
185 }
186
187 /**
188 * Build the widget list for the shell payload. Runs through
189 * every entry registered via `openstation_register_widget()` and
190 * attaches the resolved script URL (`wp_scripts()` lookup) so
191 * the shell can dynamically inject the script on mid-session
192 * plugin activation.
193 *
194 * @return array[]
195 */
196 function openstation_build_desktop_widgets_payload() {
197 $registry = openstation_desktop_widget_registry();
198 if ( ! is_array( $registry ) || empty( $registry ) ) {
199 return array();
200 }
201
202 $out = array();
203 foreach ( $registry as $entry ) {
204 $script_payload = openstation_resolve_script_payload( $entry['script'] );
205
206 $out[] = array(
207 'id' => $entry['id'],
208 'label' => $entry['label'],
209 'description' => $entry['description'],
210 'icon' => $entry['icon'],
211 'movable' => $entry['movable'],
212 'resizable' => $entry['resizable'],
213 'minWidth' => $entry['min_width'],
214 'minHeight' => $entry['min_height'],
215 'maxWidth' => $entry['max_width'],
216 'maxHeight' => $entry['max_height'],
217 'defaultWidth' => $entry['default_width'],
218 'defaultHeight' => $entry['default_height'],
219 'scriptUrl' => $script_payload['url'],
220 // The packages this widget declares, in load order.
221 // WordPress resolves a script's dependencies when it
222 // enqueues it; a widget bundle is delivered lazily and
223 // never goes through that, so a widget declaring
224 // `wp-api-fetch` found `wp.apiFetch` undefined at mount.
225 // See docs/migration-wp-package-globals.md.
226 'scriptDeps' => openstation_resolve_script_dependencies( $entry['script'] ),
227 'scriptHandle' => $entry['script'],
228 'scriptBefore' => $script_payload['before'],
229 'scriptAfter' => $script_payload['after'],
230 'scriptL10n' => $script_payload['l10n'],
231 'scriptTranslations' => $script_payload['translations'],
232 );
233 }
234 return $out;
235 }
236
237 /*
238 * Widget scripts are NOT enqueued here, and that is deliberate.
239 *
240 * Everything the widget picker shows — label, description, icon,
241 * size constraints — is metadata declared right here in PHP and
242 * shipped in the boot payload. The only thing a plugin's bundle
243 * contributes is the `mount` callback, so the shell assembles the
244 * whole def from the payload and loads the script the first time
245 * the widget is actually mounted.
246 *
247 * A widget the user has never enabled therefore costs a row in the
248 * picker and nothing else. This file used to `wp_enqueue_script()`
249 * every registered one on every admin page, which meant the nine
250 * built-in widget bundles — Drafts at 46 KB, Focus Timer at 41 KB,
251 * Notes at 31 KB, and the rest — were downloaded and parsed by
252 * every user whether or not a single widget was on their desktop.
253 *
254 * See `src/widgets/server-sync.ts`. `scriptUrl` in the payload
255 * (built above) is what makes that possible; nothing else on the
256 * PHP side is involved.
257 */
258