PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.8.9
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.8.9
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 / commands.php

commands.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.8.9, at includes/commands.php

329 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop command registration APIs.
4 *
5 * Two entry points, mirroring the rest of the plugin surface but tuned
6 * for the command-palette use case:
7 *
8 * - `desktop_mode_register_command_script( $handle )` — primary, minimum-
9 * ceremony opt-in. Tells the shell: "this enqueued script registers
10 * slash-commands; include it in the plugins-changed payload so it
11 * gets injected into the shell page mid-session when my plugin is
12 * installed or activated without a full reload."
13 *
14 * - `desktop_mode_register_command( $args )` — optional. Plugins that
15 * want to declare command metadata server-side (for discoverability
16 * tooling, REST enumeration, future pre-registration shims) use this.
17 * The `run` function still lives JS-side; metadata is advisory today.
18 *
19 * Both APIs feed `desktop_mode_build_desktop_command_scripts_payload()` and
20 * `desktop_mode_build_desktop_commands_payload()`, which contribute
21 * `serverCommandScripts` and `serverCommands` to the shell payload in
22 * `desktop_mode_build_menu_payload()`.
23 *
24 * @since 0.15.0
25 * @package WPDesktopMode
26 */
27
28 defined( 'ABSPATH' ) || exit;
29
30 /**
31 * Declare a WP-registered script handle as a command-palette provider.
32 *
33 * Example:
34 *
35 * ```php
36 * add_action( 'admin_enqueue_scripts', function () {
37 * wp_register_script(
38 * 'home-assistant-commands',
39 * plugins_url( 'js/commands.js', __FILE__ ),
40 * array( 'desktop-mode' ),
41 * '1.0.0',
42 * true
43 * );
44 * wp_enqueue_script( 'home-assistant-commands' );
45 * } );
46 * desktop_mode_register_command_script( 'home-assistant-commands' );
47 * ```
48 *
49 * The script's JS side calls `wp.desktop.registerCommand( { … } )` as
50 * usual. When a user installs or activates the plugin, the chromeless
51 * bridge's `desktop-mode-plugins-changed` payload includes the resolved
52 * script URL; the shell's command-sync module injects the script into
53 * the shell page and the new commands appear in the palette without a
54 * full reload.
55 *
56 * @since 0.15.0
57 *
58 * @param string $handle WP-registered script handle.
59 * @return true|WP_Error `true` on success; `WP_Error` on validation failure.
60 */
61 function desktop_mode_register_command_script( $handle ) {
62 $handle = (string) $handle;
63 if ( '' === $handle ) {
64 return desktop_mode_registration_error(
65 'desktop_mode_missing_handle',
66 __( 'Command script registration requires a non-empty script handle.', 'desktop-mode' )
67 );
68 }
69
70 desktop_mode_desktop_command_script_registry( $handle, true );
71
72 /**
73 * Fires after a desktop command script handle is registered.
74 *
75 * @since 0.15.0
76 *
77 * @param string $handle The registered script handle.
78 */
79 do_action( 'desktop_mode_command_script_registered', $handle );
80
81 return true;
82 }
83
84 /**
85 * Declare a desktop command server-side. Optional companion to
86 * `desktop_mode_register_command_script()` for plugins that want metadata
87 * enumerable without JS execution (REST, future pre-registration, etc.).
88 * The command's `run` function still lives JS-side; this function only
89 * stores metadata.
90 *
91 * Example:
92 *
93 * ```php
94 * desktop_mode_register_command( array(
95 * 'slug' => 'ha-lights',
96 * 'label' => __( 'Home Assistant: Lights', 'home-assistant' ),
97 * 'description' => __( 'Toggle smart lights from the palette.', 'home-assistant' ),
98 * 'icon' => 'dashicons-lightbulb',
99 * 'hint' => '[room]',
100 * 'script' => 'home-assistant-commands',
101 * ) );
102 * ```
103 *
104 * Implicitly registers the `script` handle via
105 * `desktop_mode_register_command_script()` when provided, so plugins using
106 * this API don't need to call both functions.
107 *
108 * @since 0.15.0
109 *
110 * @param array $args {
111 * @type string $slug Slash-command slug (without leading `/`). Required.
112 * @type string $label Human-readable label. Required.
113 * @type string $description Subtitle shown in the palette. Default empty.
114 * @type string $icon Dashicons class. Default `dashicons-arrow-right-alt`.
115 * @type string $hint Argument hint rendered next to the slug. Default empty.
116 * @type string $script WP script handle providing the `run` function.
117 * Registered via `desktop_mode_register_command_script()`
118 * when present. Default empty (advisory registration only).
119 * }
120 * @return true|WP_Error `true` on success; `WP_Error` on validation failure.
121 */
122 function desktop_mode_register_command( $args = array() ) {
123 $defaults = array(
124 'slug' => '',
125 'label' => '',
126 'description' => '',
127 'icon' => 'dashicons-arrow-right-alt',
128 'hint' => '',
129 'script' => '',
130 );
131 $args = wp_parse_args( $args, $defaults );
132
133 $slug = (string) $args['slug'];
134 if ( '' === $slug ) {
135 return desktop_mode_registration_error(
136 'desktop_mode_missing_slug',
137 __( 'Command registration requires a non-empty `slug`.', 'desktop-mode' )
138 );
139 }
140 if ( '' === (string) $args['label'] ) {
141 return desktop_mode_registration_error(
142 'desktop_mode_missing_label',
143 __( 'Command registration requires a non-empty `label`.', 'desktop-mode' ),
144 array( 'slug' => $slug )
145 );
146 }
147
148 $entry = array(
149 'slug' => $slug,
150 'label' => (string) $args['label'],
151 'description' => (string) $args['description'],
152 'icon' => (string) $args['icon'],
153 'hint' => (string) $args['hint'],
154 'script' => (string) $args['script'],
155 );
156 desktop_mode_desktop_command_registry( $slug, $entry );
157
158 if ( '' !== $entry['script'] ) {
159 desktop_mode_desktop_command_script_registry( $entry['script'], true );
160 }
161
162 /**
163 * Fires after a desktop command is successfully registered.
164 *
165 * @since 0.15.0
166 *
167 * @param string $slug The command slug.
168 * @param array $entry The stored registry entry.
169 */
170 do_action( 'desktop_mode_command_registered', $slug, $entry );
171
172 return true;
173 }
174
175 /**
176 * Internal module-level registry for command script handles declared
177 * via {@see desktop_mode_register_command_script()}. Accessed by both the
178 * registration API (write) and the payload builder (read).
179 *
180 * @since 0.15.0
181 * @internal
182 *
183 * @param string $handle Script handle to read or write.
184 * @param bool|null $value Pass `true` to register; `null` to read only.
185 * @return array|bool When called with no args returns the full store.
186 */
187 function desktop_mode_desktop_command_script_registry( $handle = '', $value = null ) {
188 static $store = array();
189
190 if ( '__flush__' === (string) $handle ) {
191 $store = array();
192 return array();
193 }
194 if ( '' === (string) $handle ) {
195 return $store;
196 }
197 if ( null !== $value ) {
198 $store[ (string) $handle ] = (bool) $value;
199 }
200 return isset( $store[ (string) $handle ] ) ? $store[ (string) $handle ] : false;
201 }
202
203 /**
204 * Flush the command-script registry. Tests call this in `set_up` so
205 * a previous test's stale handle doesn't leak into the next test's
206 * payload-build assertions. No production caller.
207 *
208 * @since 0.18.0
209 */
210 function desktop_mode_flush_desktop_command_script_registry() {
211 desktop_mode_desktop_command_script_registry( '__flush__' );
212 }
213
214 /**
215 * Internal module-level registry for commands declared via
216 * {@see desktop_mode_register_command()}.
217 *
218 * @since 0.15.0
219 * @internal
220 *
221 * @param string $slug Slug to read or write.
222 * @param array|null $entry Entry to store, or `null` to read.
223 * @return array|null
224 */
225 function desktop_mode_desktop_command_registry( $slug = '', $entry = null ) {
226 static $store = array();
227
228 if ( '' === (string) $slug ) {
229 return $store;
230 }
231 if ( null !== $entry ) {
232 $store[ (string) $slug ] = $entry;
233 }
234 return isset( $store[ (string) $slug ] ) ? $store[ (string) $slug ] : null;
235 }
236
237 /**
238 * Build the script-handle payload fed to the shell. Resolves each
239 * registered handle to a full payload via {@see desktop_mode_resolve_script_payload()};
240 * handles that aren't currently enqueued (plugin not active this request)
241 * resolve to an empty URL and are dropped. The harvested `extra` data
242 * (localize / inline / translations) ships alongside the URL so the
243 * shell's lazy `<script>` injection doesn't drop it the way a bare
244 * `<script src="…">` append would.
245 *
246 * @since 0.15.0
247 *
248 * @return array[] List of `{ handle, scriptUrl, scriptBefore, scriptAfter, scriptL10n, scriptTranslations }` entries.
249 */
250 function desktop_mode_build_desktop_command_scripts_payload() {
251 $registry = desktop_mode_desktop_command_script_registry();
252 if ( ! is_array( $registry ) || empty( $registry ) ) {
253 return array();
254 }
255
256 $out = array();
257 $seen = array();
258 foreach ( $registry as $handle => $active ) {
259 if ( ! $active || isset( $seen[ $handle ] ) ) {
260 continue;
261 }
262 $payload = desktop_mode_resolve_script_payload( $handle );
263 if ( '' === $payload['url'] ) {
264 desktop_mode_warn_unresolvable_script_handle(
265 'desktop_mode_register_command_script',
266 'Command',
267 (string) $handle
268 );
269 continue;
270 }
271 $out[] = array(
272 'handle' => (string) $handle,
273 'scriptUrl' => $payload['url'],
274 'scriptBefore' => $payload['before'],
275 'scriptAfter' => $payload['after'],
276 'scriptL10n' => $payload['l10n'],
277 'scriptTranslations' => $payload['translations'],
278 );
279 $seen[ $handle ] = true;
280 }
281 return $out;
282 }
283
284 /**
285 * Build the metadata payload for commands declared via
286 * {@see desktop_mode_register_command()}. Each entry carries the resolved
287 * `scriptUrl` alongside metadata so the shell's sync knows which script
288 * contributed it — enables future pre-registration shims without a round
289 * trip.
290 *
291 * @since 0.15.0
292 *
293 * @return array[]
294 */
295 function desktop_mode_build_desktop_commands_payload() {
296 $registry = desktop_mode_desktop_command_registry();
297 if ( ! is_array( $registry ) || empty( $registry ) ) {
298 return array();
299 }
300
301 $out = array();
302 foreach ( $registry as $entry ) {
303 $handle = (string) $entry['script'];
304 $payload = '' !== $handle
305 ? desktop_mode_resolve_script_payload( $handle )
306 : array(
307 'url' => '',
308 'before' => array(),
309 'after' => array(),
310 'l10n' => array(),
311 'translations' => '',
312 );
313 $out[] = array(
314 'slug' => (string) $entry['slug'],
315 'label' => (string) $entry['label'],
316 'description' => (string) $entry['description'],
317 'icon' => (string) $entry['icon'],
318 'hint' => (string) $entry['hint'],
319 'scriptUrl' => $payload['url'],
320 'scriptHandle' => $handle,
321 'scriptBefore' => $payload['before'],
322 'scriptAfter' => $payload['after'],
323 'scriptL10n' => $payload['l10n'],
324 'scriptTranslations' => $payload['translations'],
325 );
326 }
327 return $out;
328 }
329