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 / wallpapers.php

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

279 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Wallpapers registry.
4 *
5 * Server-side registration API + payload builder + asset enqueue
6 * for the desktop wallpaper picker. Wallpaper definitions live
7 * on `window.openStationWallpapers[ id ]` (set by the plugin's
8 * own JS); this module is the PHP side that announces them to
9 * the shell and ships their script handles into the boot
10 * payload.
11 *
12 * Extracted from `components.php` during the architecture-0.8.1
13 * PHP slicing (phase 6). Behaviour, function names, filter
14 * contracts, and error codes all unchanged.
15 *
16 * @package OpenStation
17 */
18
19 defined( 'ABSPATH' ) || exit;
20
21 /**
22 * Register a server-side desktop wallpaper. Symmetrical to
23 * {@see openstation_register_widget()}. The plugin's JS side
24 * publishes the full `WallpaperDef` (with mount / resolveValue /
25 * renderEditor callbacks as appropriate) on
26 * `window.openStationWallpapers[ <id> ]`; the shell loads the
27 * declared script, reads that global, and registers the def via
28 * the normal wallpaper registry. Deactivation unregisters the
29 * def and re-applies the current selection (which falls back to
30 * a built-in if the user's active wallpaper was the one leaving).
31 *
32 * Example:
33 *
34 * ```php
35 * openstation_register_wallpaper( 'myplugin/snow', array(
36 * 'label' => __( 'Snow', 'my-plugin' ),
37 * 'preview' => 'linear-gradient(#fff, #ddd)',
38 * 'type' => 'canvas',
39 * 'script' => 'my-plugin-snow-wallpaper',
40 * ) );
41 * ```
42 *
43 * ```js
44 * // Inside my-plugin-snow-wallpaper.js
45 * window.openStationWallpapers = window.openStationWallpapers || {};
46 * window.openStationWallpapers[ 'myplugin/snow' ] = {
47 * id: 'myplugin/snow',
48 * label: 'Snow',
49 * type: 'canvas',
50 * preview: 'linear-gradient(#fff, #ddd)',
51 * needs: [ 'pixijs' ],
52 * mount: function ( container, ctx ) { return function () {}; },
53 * };
54 * ```
55 *
56 * @param string $id Wallpaper id. For canvas wallpapers this must
57 * match the `window.openStationWallpapers[<id>]`
58 * key the plugin's JS publishes.
59 * @param array $args {
60 * @type string $label Picker label. Required.
61 * @type string $preview CSS value rendered in the picker
62 * swatch (gradient, color,
63 * `url(...)`, etc.). Required.
64 * @type string $type 'css' | 'canvas'. Default 'canvas'.
65 * @type string $value CSS value applied to the wallpaper
66 * surface (only relevant for `css`
67 * type — canvas wallpapers paint in
68 * JS). Defaults to `preview` so a
69 * single string covers the common
70 * case where swatch and surface are
71 * identical.
72 * @type string $script Enqueued script handle that
73 * publishes the def on the global.
74 * Required for `canvas` type;
75 * optional for `css`.
76 * @type string $description Plain-text description shown in OS
77 * Settings when the wallpaper is the
78 * active selection — what it is, where
79 * its data comes from, the story behind
80 * it. Optional.
81 * @type string[] $capabilities Gate: ALL caps must match. Any
82 * missed cap returns
83 * `WP_Error openstation_capability_denied`.
84 * }
85 * @return true|WP_Error `true` on success; `WP_Error` otherwise.
86 */
87 function openstation_register_wallpaper( $id, $args = array() ) {
88 $id = (string) $id;
89 if ( '' === $id ) {
90 return openstation_registration_error(
91 'openstation_missing_id',
92 __( 'Wallpaper id is required.', 'desktop-mode' )
93 );
94 }
95
96 $defaults = array(
97 'label' => '',
98 'preview' => '',
99 'type' => 'canvas',
100 'value' => '',
101 'script' => '',
102 'description' => '',
103 'capabilities' => array(),
104 );
105 $args = wp_parse_args( $args, $defaults );
106
107 foreach ( (array) $args['capabilities'] as $cap ) {
108 if ( ! current_user_can( (string) $cap ) ) {
109 return openstation_registration_error(
110 'openstation_capability_denied',
111 sprintf(
112 /* translators: %s: capability slug. */
113 __( 'Current user lacks the %s capability required to register this wallpaper.', 'desktop-mode' ),
114 (string) $cap
115 ),
116 array(
117 'capability' => (string) $cap,
118 'id' => $id,
119 )
120 );
121 }
122 }
123 if ( '' === (string) $args['label'] ) {
124 return openstation_registration_error(
125 'openstation_missing_label',
126 __( 'Wallpaper registration requires a non-empty `label`.', 'desktop-mode' ),
127 array( 'id' => $id )
128 );
129 }
130 $type = in_array( $args['type'], array( 'css', 'canvas' ), true )
131 ? $args['type']
132 : 'canvas';
133 // Canvas wallpapers always need a script (the def with its
134 // `mount` callback is published on the JS global by that
135 // script). CSS wallpapers can skip the script — the shell can
136 // render from the `value` / `preview` string alone.
137 if ( 'canvas' === $type && '' === (string) $args['script'] ) {
138 return openstation_registration_error(
139 'openstation_missing_script',
140 __( 'Canvas wallpaper registration requires a `script` handle that publishes the def.', 'desktop-mode' ),
141 array( 'id' => $id )
142 );
143 }
144
145 // `value` defaults to `preview` when omitted — the common case
146 // for a plain gradient/solid where the swatch and the surface
147 // render the same CSS. Authors can split them (e.g. static
148 // swatch preview + animated value) by passing both.
149 $value = (string) $args['value'];
150 if ( '' === $value ) {
151 $value = (string) $args['preview'];
152 }
153
154 $entry = array(
155 'id' => $id,
156 // Plain text by contract, same as `description` below. The
157 // shell paints labels through the `html` tagged template, whose
158 // text slots build DOM with `createTextNode()` — never
159 // `innerHTML` — so a label cannot become markup downstream.
160 //
161 // Note this STRIPS rather than ESCAPES, and that distinction is
162 // load-bearing: `esc_html()` here would encode `&` in a
163 // perfectly ordinary label ("Black & White") and the text node
164 // would then render the entity literally as `&amp;`. Escaping
165 // belongs at an HTML boundary; there isn't one on this path.
166 'label' => sanitize_text_field( (string) $args['label'] ),
167 'preview' => (string) $args['preview'],
168 'type' => $type,
169 'value' => $value,
170 'script' => (string) $args['script'],
171 // Plain text by contract — the shell renders it as text, never
172 // as HTML, so strip tags here rather than trusting every caller.
173 'description' => sanitize_textarea_field( (string) $args['description'] ),
174 );
175 openstation_desktop_wallpaper_registry( $id, $entry );
176
177 /**
178 * Fires after a desktop wallpaper is successfully registered.
179 *
180 * Does NOT fire when `openstation_register_wallpaper()` returns a
181 * `WP_Error`.
182 *
183 * @param string $id The wallpaper id.
184 * @param array $entry The stored registry entry.
185 */
186 do_action( 'openstation_wallpaper_registered', $id, $entry );
187
188 return true;
189 }
190
191 /**
192 * Internal module-level registry for wallpapers registered via
193 * {@see openstation_register_wallpaper()}. Same static-store
194 * pattern as the widget + native-window registries.
195 *
196 * @internal
197 */
198 function openstation_desktop_wallpaper_registry( $id = '', $entry = null ) {
199 static $store = array();
200
201 if ( '' === (string) $id ) {
202 return $store;
203 }
204 if ( null !== $entry ) {
205 $store[ $id ] = $entry;
206 }
207 return isset( $store[ $id ] ) ? $store[ $id ] : null;
208 }
209
210 /**
211 * Build the wallpaper list for the shell payload. Only metadata +
212 * the resolved script URL cross the wire; the plugin's mount
213 * callback is announced via the JS global the script sets up.
214 *
215 * @return array[]
216 */
217 function openstation_build_desktop_wallpapers_payload() {
218 $registry = openstation_desktop_wallpaper_registry();
219 if ( ! is_array( $registry ) || empty( $registry ) ) {
220 return array();
221 }
222 /**
223 * Filters the server-declared wallpaper list before it ships to
224 * the shell. Mirrors the JS-side `os.wallpapers` filter
225 * so plugins can rearrange, hide, or override entries at boot
226 * without round-tripping through the JS registry.
227 *
228 * @param array[] $registry The registered wallpaper entries.
229 */
230 $registry = apply_filters( 'openstation_wallpapers', $registry );
231 if ( ! is_array( $registry ) ) {
232 return array();
233 }
234 $out = array();
235 foreach ( $registry as $entry ) {
236 if ( ! is_array( $entry ) || empty( $entry['id'] ) ) {
237 continue;
238 }
239 $handle = isset( $entry['script'] ) ? (string) $entry['script'] : '';
240 $payload = openstation_resolve_script_payload( $handle );
241 $out[] = array(
242 'id' => (string) $entry['id'],
243 'label' => isset( $entry['label'] ) ? (string) $entry['label'] : '',
244 'preview' => isset( $entry['preview'] ) ? (string) $entry['preview'] : '',
245 'type' => isset( $entry['type'] ) ? (string) $entry['type'] : 'canvas',
246 'value' => isset( $entry['value'] ) ? (string) $entry['value'] : '',
247 'description' => isset( $entry['description'] ) ? (string) $entry['description'] : '',
248 'scriptUrl' => $payload['url'],
249 'scriptHandle' => $handle,
250 'scriptBefore' => $payload['before'],
251 'scriptAfter' => $payload['after'],
252 'scriptL10n' => $payload['l10n'],
253 'scriptTranslations' => $payload['translations'],
254 );
255 }
256 return $out;
257 }
258
259
260 /*
261 * Wallpaper scripts are NOT enqueued here, and that is deliberate.
262 *
263 * A canvas wallpaper's bundle IS the wallpaper — Living Tree is 58 KB
264 * of PixiJS scene, Snow is 42 KB — and this file used to
265 * `wp_enqueue_script()` every registered one on every admin page, so
266 * that every user downloaded and parsed every wallpaper in the
267 * install including the ones they were not wearing. The metadata in
268 * the boot payload (label, preview swatch, description) is enough for
269 * the shell to register a stub and paint a picker tile without any of
270 * it.
271 *
272 * The bundle arrives when something needs the callbacks: the shell
273 * hydrates the user's ACTIVE wallpaper during the boot sync, and the
274 * wallpaper picker hydrates the rest when it opens. See
275 * `src/wallpapers/lazy.ts`. `scriptUrl` in the payload (built above)
276 * is what makes that possible; nothing else on the PHP side is
277 * involved.
278 */
279