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

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

331 lines 11.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Native-window tabs registry.
4 *
5 * Multi-tab native windows are a sister-to-the-window
6 * registration: a tab is owned by some window id and its content
7 * is wrapped in `<os-tabpanel>` automatically by the shell. The
8 * MAIN_TAB constant reserves the `'main'` value for the window's
9 * own `template` callback so plugins can't accidentally collide
10 * with the built-in main pane.
11 *
12 * Extracted from `components.php` during the architecture-0.8.1
13 * PHP slicing (phase 6).
14 *
15 * @package OpenStation
16 */
17
18 defined( 'ABSPATH' ) || exit;
19
20 /**
21 * Reserved tab value for the window's own `template` output. The
22 * main tab always renders first, its markup comes from the window
23 * registration's `template` callback, and its label is the
24 * window's `main_tab_label` (falling back to the window `title`).
25 *
26 * Plugins cannot register an additional tab with this value —
27 * {@see openstation_register_window_tab()} returns
28 * `openstation_reserved_tab_value` when they try.
29 */
30 const OPENSTATION_NATIVE_WINDOW_MAIN_TAB = 'main';
31
32 /**
33 * Register an additional tab on an existing native window.
34 *
35 * Mirrors the legacy iframe-window ergonomics where submenus
36 * auto-become tabs below the title bar: the window's own
37 * `template` renders as the first tab (labelled by `main_tab_label`
38 * / `title`), and every call to this function adds another tab
39 * alongside it. Cross-plugin extension is supported — a companion
40 * plugin can attach a tab to someone else's window.
41 *
42 * Registering even a single tab turns on the auto-wrap path in
43 * `openstation_build_native_window_template_html()`: the shell wraps the
44 * window body in `<os-stack>` + `<os-tabs>` + `<os-tabpanel>`
45 * elements automatically. Plugin authors no longer hand-write that
46 * markup — the shell provides it and `<os-tabpanel>` auto-swap
47 * handles visibility.
48 *
49 * ```php
50 * // Plugin that owns the window declares its own tabs:
51 * openstation_register_window( 'jorvy', array(
52 * 'title' => 'Jorvy',
53 * 'main_tab_label' => 'Quotes',
54 * 'template' => function () { echo '<p class="quote"></p>'; },
55 * 'script' => 'jorvy-main',
56 * ) );
57 * openstation_register_window_tab( 'jorvy', array(
58 * 'value' => 'about',
59 * 'label' => 'About',
60 * 'template' => function () { echo '<p>Marvel quotes, rotated every 10s.</p>'; },
61 * ) );
62 *
63 * // A companion plugin attaches a tab to someone else's window:
64 * openstation_register_window_tab( 'jorvy', array(
65 * 'value' => 'stats',
66 * 'label' => 'Stats',
67 * 'template' => 'jorvy_stats_pane',
68 * 'script' => 'jorvy-stats',
69 * ) );
70 * ```
71 *
72 * @param string $window_id Id of the native window this tab belongs to.
73 * @param array $args {
74 * @type string $value Tab id (unique within the window).
75 * Required. Cannot equal the reserved
76 * value `main` — that's the window's
77 * own template tab.
78 * @type string $label Display label on the tab strip. Required.
79 * @type callable $template Callback that echoes the tab's
80 * pane HTML. Wrapped in
81 * `<os-tabpanel for="<value>">` by
82 * the shell. Required.
83 * @type string $script Optional script handle enqueued
84 * when the window is active — useful
85 * when a tab needs its own JS module
86 * without bloating the main window
87 * script. Default empty.
88 * @type int $position Sort order among tabs on this
89 * window; lower renders earlier.
90 * Default 100.
91 * @type string[] $capabilities Gate: ALL caps must match. Any
92 * missed cap returns
93 * `WP_Error openstation_capability_denied`.
94 * }
95 * @return true|WP_Error `true` on success; `WP_Error` otherwise.
96 */
97 function openstation_register_window_tab( $window_id, $args = array() ) {
98 $window_id = sanitize_key( (string) $window_id );
99 if ( '' === $window_id ) {
100 return openstation_registration_error(
101 'openstation_missing_window_id',
102 __( 'Window id is required when registering a tab.', 'desktop-mode' )
103 );
104 }
105
106 $defaults = array(
107 'value' => '',
108 'label' => '',
109 'template' => null,
110 'script' => '',
111 'position' => 100,
112 'capabilities' => array(),
113 );
114 $args = wp_parse_args( $args, $defaults );
115
116 foreach ( (array) $args['capabilities'] as $cap ) {
117 if ( ! current_user_can( (string) $cap ) ) {
118 return openstation_registration_error(
119 'openstation_capability_denied',
120 sprintf(
121 /* translators: %s: capability slug. */
122 __( 'Current user lacks the %s capability required to register this window tab.', 'desktop-mode' ),
123 (string) $cap
124 ),
125 array(
126 'capability' => (string) $cap,
127 'window_id' => $window_id,
128 )
129 );
130 }
131 }
132
133 // Tab values accept both flat slugs ('convert') and a single
134 // `vendor/sub-id` namespace ('plugin/convert') so two plugins
135 // targeting the same window can ship same-named tabs without
136 // stomping each other in the registry. The downstream uses
137 // (`os-tabpanel[for="…"]`, `<os-tab value="…">`) all pass the
138 // value through esc_attr and use it as an attribute selector,
139 // which tolerates the slash.
140 $value_raw = strtolower( trim( (string) $args['value'] ) );
141 if ( '' === $value_raw ) {
142 return openstation_registration_error(
143 'openstation_missing_tab_value',
144 __( 'Window tab registration requires a non-empty `value`.', 'desktop-mode' ),
145 array( 'window_id' => $window_id )
146 );
147 }
148 if ( ! preg_match( '/^[a-z0-9_-]+(\/[a-z0-9_-]+)?$/', $value_raw ) ) {
149 return openstation_registration_error(
150 'openstation_invalid_tab_value',
151 sprintf(
152 /* translators: %s: the invalid value. */
153 __( 'Window tab `value` "%s" must match /^[a-z0-9_-]+(\/[a-z0-9_-]+)?$/ — lowercase alphanum + hyphen/underscore, with at most one `vendor/sub-id` slash.', 'desktop-mode' ),
154 $value_raw
155 ),
156 array(
157 'window_id' => $window_id,
158 'value' => $value_raw,
159 )
160 );
161 }
162 $value = $value_raw;
163 if ( OPENSTATION_NATIVE_WINDOW_MAIN_TAB === $value ) {
164 return openstation_registration_error(
165 'openstation_reserved_tab_value',
166 sprintf(
167 /* translators: %s: the reserved value. */
168 __( 'The tab value "%s" is reserved for the window\'s own template tab.', 'desktop-mode' ),
169 OPENSTATION_NATIVE_WINDOW_MAIN_TAB
170 ),
171 array(
172 'window_id' => $window_id,
173 'value' => $value,
174 )
175 );
176 }
177 if ( '' === (string) $args['label'] ) {
178 return openstation_registration_error(
179 'openstation_missing_label',
180 __( 'Window tab registration requires a non-empty `label`.', 'desktop-mode' ),
181 array( 'window_id' => $window_id )
182 );
183 }
184 if ( ! is_callable( $args['template'] ) ) {
185 return openstation_registration_error(
186 'openstation_invalid_template',
187 __( 'Window tab registration requires a callable `template` that echoes the pane body.', 'desktop-mode' ),
188 array( 'window_id' => $window_id )
189 );
190 }
191
192 $entry = array(
193 'value' => $value,
194 'label' => (string) $args['label'],
195 'template' => $args['template'],
196 'script' => (string) $args['script'],
197 'position' => (int) $args['position'],
198 );
199 openstation_desktop_window_tab_registry( $window_id, $value, $entry );
200
201 /**
202 * Fires after a native window tab is successfully registered.
203 *
204 * Does NOT fire when `openstation_register_window_tab()` returns
205 * a `WP_Error`.
206 *
207 * @param string $window_id The window this tab belongs to.
208 * @param string $value The tab value.
209 * @param array $entry The stored registry entry.
210 */
211 do_action( 'openstation_window_tab_registered', $window_id, $value, $entry );
212
213 return true;
214 }
215
216 /**
217 * Internal nested registry for native-window tabs keyed by
218 * `[window_id][value]`. Pass `$entry = null` and any non-empty
219 * `$value` to read a single entry; pass both `$window_id` and
220 * `$value` empty to get the full registry.
221 *
222 * @internal
223 *
224 * @param string $window_id Window id (or '' to read everything).
225 * @param string $value Tab value (or '' to read every tab
226 * on the given window).
227 * @param array|null $entry Entry to store, or null to just read.
228 * @return array|null
229 */
230 function openstation_desktop_window_tab_registry( $window_id = '', $value = '', $entry = null ) {
231 static $store = array();
232
233 if ( '' === (string) $window_id ) {
234 return $store;
235 }
236 if ( ! isset( $store[ $window_id ] ) ) {
237 $store[ $window_id ] = array();
238 }
239 if ( '' === (string) $value ) {
240 return $store[ $window_id ];
241 }
242 if ( null !== $entry ) {
243 $store[ $window_id ][ $value ] = $entry;
244 }
245 return isset( $store[ $window_id ][ $value ] )
246 ? $store[ $window_id ][ $value ]
247 : null;
248 }
249
250 /**
251 * Return the ordered list of tab descriptors for a window. The
252 * main tab (reserved value `main`) is always first; additional
253 * tabs follow in `position` order (ties broken by registration
254 * order).
255 *
256 * Shape per entry: `{ value, label, template, script, is_main, position }`.
257 *
258 * Filterable via `openstation_window_tabs` so a late-loading plugin
259 * can reorder, hide, or relabel tabs another plugin registered —
260 * mirrors the `openstation_wallpapers` filter discipline.
261 *
262 * @param string $window_id Window id.
263 * @return array[]
264 */
265 function openstation_get_native_window_tabs( $window_id ) {
266 $window = openstation_native_window_registry( (string) $window_id );
267 if ( ! is_array( $window ) ) {
268 return array();
269 }
270
271 $extras = openstation_desktop_window_tab_registry( $window_id );
272 if ( ! is_array( $extras ) ) {
273 $extras = array();
274 }
275
276 // Main tab first — label falls back to the window title when no
277 // `main_tab_label` was set during registration.
278 $main_label = '' !== (string) $window['main_tab_label']
279 ? (string) $window['main_tab_label']
280 : (string) $window['title'];
281 $tabs = array(
282 array(
283 'value' => OPENSTATION_NATIVE_WINDOW_MAIN_TAB,
284 'label' => $main_label,
285 'template' => $window['template'],
286 'script' => '',
287 'is_main' => true,
288 'position' => 0,
289 ),
290 );
291
292 // Additional tabs sorted by position. Values are trusted — they
293 // were validated against /^[a-z0-9_-]+(\/[a-z0-9_-]+)?$/ at
294 // registration time (sanitize_key would strip the namespace slash).
295 $sorted = array_values( $extras );
296 usort(
297 $sorted,
298 static function ( $a, $b ) {
299 if ( $a['position'] === $b['position'] ) {
300 return 0;
301 }
302 return $a['position'] < $b['position'] ? -1 : 1;
303 }
304 );
305 foreach ( $sorted as $tab ) {
306 $tabs[] = array(
307 'value' => $tab['value'],
308 'label' => $tab['label'],
309 'template' => $tab['template'],
310 'script' => $tab['script'],
311 'is_main' => false,
312 'position' => $tab['position'],
313 );
314 }
315
316 /**
317 * Filters the full ordered tab list for a native window right
318 * before the shell renders it. Return a reshaped array to
319 * reorder, hide, or rename tabs — same shape as the input.
320 *
321 * The main tab's `template` is the window's own template
322 * callback; replacing it at filter time is supported but
323 * unusual — prefer updating the window registration itself.
324 *
325 * @param array[] $tabs Ordered tab descriptors.
326 * @param string $window_id Window id.
327 */
328 $filtered = apply_filters( 'openstation_window_tabs', $tabs, $window_id );
329 return is_array( $filtered ) ? $filtered : $tabs;
330 }
331