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 / window-notices.php

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

255 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 * Window notices — declarative top-of-window banners.
4 *
5 * Plugins call `openstation_register_window_notice()` to surface a
6 * tone-coded banner at the top of every window matching a `match`
7 * predicate (or every window by default). The shell renders the
8 * notice via the `<os-notice>` web component inside the window's
9 * `after-titlebar` slot, and records the user's dismissal in
10 * `localStorage` so the same notice never reappears.
11 *
12 * Notices are pure declarative data — no JS handle is needed.
13 *
14 * Example:
15 *
16 * ```php
17 * openstation_register_window_notice( array(
18 * 'id' => 'my-plugin/welcome',
19 * 'tone' => 'info',
20 * 'message' => '<strong>Welcome!</strong> Read the <a href="…">docs</a>.',
21 * 'match' => array( 'window' => 'edit-php' ), // optional
22 * ) );
23 * ```
24 *
25 * @package OpenStation
26 */
27
28 defined( 'ABSPATH' ) || exit;
29
30 /**
31 * Allowed tones — mirror the `<os-notice>` component's `tone`
32 * attribute.
33 *
34 * @return string[]
35 */
36 function openstation_window_notice_tones() {
37 return array( 'info', 'success', 'warning', 'error', 'danger', 'neutral' );
38 }
39
40 /**
41 * Register (or replace) a declarative window notice.
42 *
43 * @param array $args {
44 * @type string $id Required. Persistence + dedupe key.
45 * Recommended format `<plugin>/<slug>`.
46 * @type string $message Required. HTML body. Passed through
47 * `wp_kses_post()` before shipping, so
48 * links + basic formatting are allowed
49 * but `<script>` and other unsafe
50 * markup are stripped.
51 * @type string $tone Optional. One of
52 * {@see openstation_window_notice_tones()}.
53 * Default `info`.
54 * @type bool $dismissible Optional. Show a close button.
55 * Default `true`.
56 * @type string $icon Optional. Dashicons class for a
57 * leading glyph (e.g.
58 * `dashicons-info`). Must match
59 * `/^dashicons-[a-z0-9-]+$/`; values
60 * that don't are silently dropped to
61 * `''` rather than failing the
62 * registration.
63 * @type array $match Optional. Per-window selector. Pick
64 * any of:
65 * - `'window' => 'edit-php'` — single
66 * window id (e.g. `edit-php` for
67 * Posts, `plugins` for the native
68 * Plugins window).
69 * - `'windows' => array( 'edit-php',
70 * 'edit-php-pagename' )` — multiple
71 * window ids ("all windows of kind
72 * X / Y / Z"). Within the array the
73 * semantics is OR (any id matches).
74 * - `'urlContains' => 'wc-admin'` —
75 * case-insensitive URL substring
76 * match. Useful for plugin pages
77 * whose id is derived from a long
78 * URL.
79 *
80 * When more than one selector type is
81 * present, all of them must match
82 * (AND). E.g. `array( 'windows' =>
83 * array( 'edit-php' ), 'urlContains'
84 * => 'wc-admin' )` shows the notice
85 * only on the Posts window when its
86 * URL also contains `wc-admin` — not
87 * on every Posts window AND every
88 * wc-admin-URL window. Use two
89 * separate `openstation_register_window_notice()`
90 * calls for OR-across-selector-types.
91 *
92 * When omitted, the notice paints on
93 * every window.
94 * @type int $order Optional. Sort order — lower
95 * renders higher in a stack of
96 * notices. Default 100.
97 * }
98 * @return true|WP_Error `true` on success; `WP_Error` on validation
99 * failure.
100 */
101 function openstation_register_window_notice( $args = array() ) {
102 $defaults = array(
103 'id' => '',
104 'message' => '',
105 'tone' => 'info',
106 'dismissible' => true,
107 'icon' => '',
108 'match' => array(),
109 'order' => 100,
110 );
111 $args = wp_parse_args( $args, $defaults );
112
113 $id = (string) $args['id'];
114 if ( '' === $id ) {
115 return openstation_registration_error(
116 'openstation_missing_id',
117 __( 'Window notice registration requires a non-empty `id`.', 'desktop-mode' )
118 );
119 }
120 if ( ! preg_match( '/^[a-z0-9_\\/-]+$/i', $id ) ) {
121 return openstation_registration_error(
122 'openstation_invalid_id',
123 __( 'Window notice `id` must be alphanumeric with hyphens, underscores, or slashes.', 'desktop-mode' ),
124 array( 'id' => $id )
125 );
126 }
127
128 if ( '' === (string) $args['message'] ) {
129 return openstation_registration_error(
130 'openstation_missing_message',
131 __( 'Window notice registration requires a non-empty `message`.', 'desktop-mode' ),
132 array( 'id' => $id )
133 );
134 }
135
136 $tone = (string) $args['tone'];
137 if ( ! in_array( $tone, openstation_window_notice_tones(), true ) ) {
138 return openstation_registration_error(
139 'openstation_invalid_tone',
140 __( 'Window notice `tone` must be one of the documented values.', 'desktop-mode' ),
141 array(
142 'id' => $id,
143 'tone' => $tone,
144 )
145 );
146 }
147
148 // Icon validation. Dashicons class names are alphanumeric with
149 // hyphens and always start with `dashicons-`. Anything else is
150 // either a typo or attempted styling-injection; drop silently
151 // rather than reject the whole registration so a minor mistake
152 // in one field doesn't kill the banner entirely.
153 $icon_raw = (string) $args['icon'];
154 $icon = preg_match( '/^dashicons-[a-z0-9-]+$/', $icon_raw ) ? $icon_raw : '';
155
156 $entry = array(
157 'id' => strtolower( $id ),
158 'message' => wp_kses_post( (string) $args['message'] ),
159 'tone' => $tone,
160 'dismissible' => (bool) $args['dismissible'],
161 'icon' => $icon,
162 'match' => is_array( $args['match'] ) ? $args['match'] : array(),
163 'order' => (int) $args['order'],
164 );
165
166 openstation_window_notice_registry( $entry['id'], $entry );
167
168 /**
169 * Fires after a window notice is successfully registered.
170 *
171 * @param string $id The notice id.
172 * @param array $entry The stored registry entry.
173 */
174 do_action( 'openstation_window_notice_registered', $entry['id'], $entry );
175
176 return true;
177 }
178
179 /**
180 * Internal module-level registry for window notices.
181 *
182 * @internal
183 *
184 * @param string $id Id to read or write.
185 * @param array|null $entry Entry to store, or `null` to read.
186 * @return array|null
187 */
188 function openstation_window_notice_registry( $id = '', $entry = null ) {
189 static $store = array();
190
191 if ( '__flush__' === (string) $id ) {
192 $store = array();
193 return array();
194 }
195 if ( '' === (string) $id ) {
196 return $store;
197 }
198 if ( null !== $entry ) {
199 $store[ (string) $id ] = $entry;
200 }
201 return isset( $store[ (string) $id ] ) ? $store[ (string) $id ] : null;
202 }
203
204 /**
205 * Drop every registered window notice. Intended for PHPUnit `set_up()`
206 * so a previous test's notices can't leak into the next test's
207 * payload-build assertions. **Not** a public API — production code
208 * that calls this would wipe every plugin's registered notice on the
209 * current request. Mirrors the same `flush_*` shape the
210 * commands / settings-tabs / window-chrome registries expose.
211 *
212 * @internal
213 */
214 function openstation_flush_window_notice_registry() {
215 openstation_window_notice_registry( '__flush__' );
216 }
217
218 /**
219 * Build the payload shipped to the shell. Each entry is the stored
220 * registry record, runnable through the
221 * `openstation_window_notices` filter so plugins can mutate the
222 * final list (e.g. add a dynamic notice computed at request time).
223 *
224 * @return array[]
225 */
226 function openstation_build_window_notices_payload() {
227 $registry = openstation_window_notice_registry();
228 $entries = is_array( $registry ) ? array_values( $registry ) : array();
229
230 /**
231 * Filter the assembled list of window notices before it ships to
232 * the shell. Plugins can append/remove/mutate notices here for
233 * request-dependent banners (e.g. "your trial expires today")
234 * without registering them statically.
235 *
236 * @param array[] $entries List of notice entries.
237 */
238 $entries = apply_filters( 'openstation_window_notices', $entries );
239
240 // Re-sort by order for deterministic emission, then by id.
241 usort(
242 $entries,
243 static function ( $a, $b ) {
244 $oa = isset( $a['order'] ) ? (int) $a['order'] : 100;
245 $ob = isset( $b['order'] ) ? (int) $b['order'] : 100;
246 if ( $oa !== $ob ) {
247 return $oa - $ob;
248 }
249 return strcmp( (string) $a['id'], (string) $b['id'] );
250 }
251 );
252
253 return $entries;
254 }
255