PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.13
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.13
1.1.13 1.1.12 1.1.11 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 All 37 releases
← All changes | includes/os-settings.php +473 -99 0.9.8 → 1.1.13 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — OS Settings Persistence.
3 + * OpenStation — OS Settings Persistence.
4 4 *
5 5 * Persists each user's OS Settings preferences (wallpaper, accent color,
6 6 * dock size, custom gradient/image, HD-only toggle, and AI integration
7 7 * settings) to user meta so they survive across browsers, devices, and
@@ -8,22 +8,33 @@
8 8 * private/incognito sessions. The JS layer writes to localStorage on
9 9 * every change for instant read-back, then asynchronously syncs to this
10 10 * endpoint so user meta is the durable source of truth.
11 11 *
12 - * @package WPDesktopMode
12 + * @package OpenStation
13 13 */
14 14
15 15 defined( 'ABSPATH' ) || exit;
16 16
17 -/** User meta key for OS Settings. */
18 -const DESKTOP_MODE_OS_SETTINGS_META_KEY = 'desktop_mode_os_settings';
17 +/**
18 + * User meta key for OS Settings.
19 + *
20 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
21 + * persisted or externally-visible identifier, so renaming it would
22 + * orphan data already written by live installs (or break a live
23 + * URL). The mismatch between this constant's name and its value is
24 + * deliberate — it is NOT a half-finished rename.
25 + */
26 +const OPENSTATION_OS_SETTINGS_META_KEY = 'desktop_mode_os_settings';
19 27
20 28 /** Valid dock-size IDs — mirrors the TS `DOCK_SIZES` constant. */
21 -const DESKTOP_MODE_OS_SETTINGS_DOCK_SIZES = array( 'compact', 'default', 'large' );
29 +const OPENSTATION_OS_SETTINGS_DOCK_SIZES = array( 'compact', 'default', 'large' );
22 30
23 31 /** Valid window-radius IDs — mirrors the TS `WINDOW_RADII` constant. */
24 -const DESKTOP_MODE_OS_SETTINGS_WINDOW_RADII = array( 'sharp', 'default', 'round' );
32 +const OPENSTATION_OS_SETTINGS_WINDOW_RADII = array( 'sharp', 'default', 'round' );
25 33
34 +/** How a newly opened window lands — mirrors the TS `OPEN_WINDOWS_AS` constant. */
35 +const OPENSTATION_OS_SETTINGS_OPEN_WINDOWS_AS = array( 'default', 'maximized', 'focused' );
36 +
26 37 /**
27 38 * Valid admin-bar mode IDs — mirrors the TS `ADMIN_BAR_MODES` constant.
28 39 *
29 40 * `static` keeps the WordPress admin bar pinned above the shell (the
@@ -29,14 +40,43 @@
29 40 * `static` keeps the WordPress admin bar pinned above the shell (the
30 41 * default), `dynamic` auto-hides it to a peek strip that reveals on
31 42 * hover or keyboard focus, and `hidden` removes it entirely.
32 43 */
33 -const DESKTOP_MODE_OS_SETTINGS_ADMIN_BAR_MODES = array( 'static', 'dynamic', 'hidden' );
44 +const OPENSTATION_OS_SETTINGS_ADMIN_BAR_MODES = array( 'static', 'dynamic', 'hidden' );
34 45
35 46 /** Valid desktop-layout IDs — mirrors the TS `DESKTOP_LAYOUTS` constant. */
36 -const DESKTOP_MODE_OS_SETTINGS_DESKTOP_LAYOUTS = array( 'classic', 'unified', 'spatial' );
47 +const OPENSTATION_OS_SETTINGS_DESKTOP_LAYOUTS = array( 'classic', 'unified' );
37 48
38 49 /**
50 + * Valid dock-placement IDs — mirrors the TS `DOCK_PLACEMENTS` constant.
51 + *
52 + * Which edge the single dock sits on. Read by the layout dispatcher for
53 + * `unified`; `classic` derives its two rails from the layout itself and
54 + * ignores this.
55 + */
56 +const OPENSTATION_OS_SETTINGS_DOCK_PLACEMENTS = array( 'bottom', 'left', 'right' );
57 +
58 +/**
59 + * Valid dock-behavior IDs — mirrors the TS `DOCK_BEHAVIORS` constant.
60 + *
61 + * `static` keeps the dock always on screen (the default); `dynamic`
62 + * parks it off its edge behind a peek strip that reveals when the
63 + * pointer reaches that edge or something on it takes keyboard focus,
64 + * and releases the band it floats over from the work area.
65 + */
66 +const OPENSTATION_OS_SETTINGS_DOCK_BEHAVIORS = array( 'static', 'dynamic' );
67 +
68 +/**
69 + * `mobileLayout` — which experience the shell renders. `auto`
70 + * follows the viewport; the other two force it either way.
71 + * Mirrors `OsModePreference` in `src/mode/index.ts`.
72 + */
73 +const OPENSTATION_OS_SETTINGS_MOBILE_LAYOUTS = array( 'auto', 'desktop', 'mobile' );
74 +
75 +/** `mobileTabs` — at most this many ids pinned to the phone tab bar. */
76 +const OPENSTATION_OS_SETTINGS_MOBILE_TABS_MAX = 3;
77 +
78 +/**
39 79 * Playable range for the window-reveal duration override, in ms.
40 80 * Mirrors `MIN_REVEAL_DURATION_MS` / `MAX_REVEAL_DURATION_MS` in
41 81 * `src/reveals/registry.ts`.
42 82 *
@@ -42,10 +82,10 @@
42 82 *
43 83 * `0` sits OUTSIDE this range on purpose: it is the "no override"
44 84 * sentinel, not a duration, and is handled before the clamp.
45 85 */
46 -const DESKTOP_MODE_OS_SETTINGS_REVEAL_DURATION_MIN = 80;
47 -const DESKTOP_MODE_OS_SETTINGS_REVEAL_DURATION_MAX = 4000;
86 +const OPENSTATION_OS_SETTINGS_REVEAL_DURATION_MIN = 80;
87 +const OPENSTATION_OS_SETTINGS_REVEAL_DURATION_MAX = 4000;
48 88
49 89 /**
50 90 * Returns a well-shaped default OS settings array.
51 91 *
@@ -53,18 +93,50 @@
53 93 * gets the same starting state in both environments.
54 94 *
55 95 * @return array
56 96 */
57 -function desktop_mode_default_os_settings() {
97 +function openstation_default_os_settings() {
58 98 return array(
59 - 'wallpaper' => 'dark',
60 - 'accent' => 'wp-blue',
99 + 'wallpaper' => 'galaxy',
100 + // Pulse, the brand's signature. Mirrors `DEFAULTS` in
101 + // `src/settings/constants.ts`.
102 + 'accent' => 'pulse',
103 + // Only read when `accent` is `custom`. Seeded with Pulse so
104 + // picking Custom before touching the wheel is a no-op rather
105 + // than a jump to black. Mirrors `DEFAULTS` in
106 + // `src/settings/constants.ts`.
107 + 'customAccent' => '#f252fc',
61 108 'dockSize' => 'default',
62 - 'windowRadius' => 'default',
109 + // `round` (16px), not the preset id literally named `default`.
110 + // Preset ids are stored values and cannot be renamed, so the
111 + // option labelled "Default" in the picker is no longer the
112 + // shipped default. Must stay in step with `DEFAULTS` in
113 + // `src/settings/constants.ts` — PHP seeds the first load and JS
114 + // owns every paint after it, so a mismatch shows up as the
115 + // corners changing shape a moment after the shell boots.
116 + 'windowRadius' => 'round',
117 + 'openWindowsAs' => 'default',
63 118 // How the WordPress admin bar presents above the shell.
64 - // `static` is vanilla behavior and the shipped default.
65 - 'adminBarMode' => 'static',
66 - 'desktopLayout' => 'classic',
119 + // `hidden` ships as the default so a fresh desktop has ONE
120 + // navigation surface: everything the user can open lives on the
121 + // dock, and the dock's "Exit OpenStation" tile is the way back
122 + // to classic admin. `static` (vanilla behavior) and `dynamic`
123 + // are one pick away in OpenStation Preferences → Appearance.
124 + 'adminBarMode' => 'hidden',
125 + // Always on screen. `dynamic` (auto-hide behind a peek strip)
126 + // is one pick away in OpenStation Preferences → Appearance.
127 + 'dockBehavior' => 'static',
128 + // The Split layout's sidebar answers for itself: a folded
129 + // sidebar over a static bottom dock is a valid desk.
130 + 'sideDockBehavior' => 'static',
131 + // One dock holding every menu, with the system tiles grouped
132 + // behind a hairline. `classic` (side bar for core menus + bottom
133 + // dock for plugins) is the other option; it is no longer what a
134 + // first-run desktop looks like.
135 + 'desktopLayout' => 'unified',
136 + // Which edge the single dock sits on. Ignored by `classic`,
137 + // which derives both of its rails from the layout.
138 + 'dockPlacement' => 'bottom',
67 139 'dockRailRenderer' => 'default',
68 140 // Active desktop-theme slug, or `''` for the system default.
69 141 // Site-wide library (`includes/desktop-themes/`), per-user
70 142 // activation. Not validated against the installed list here —
@@ -126,9 +198,9 @@
126 198 'ai' => array(
127 199 'enabled' => false, // AI assistant is opt-in; enabled from OS Settings → Features once a provider is configured.
128 200 ),
129 201 // Per-user opt-IN for the native Posts window. When true,
130 - // clicking the Posts dock tile opens the `<wpd-table>`-driven
202 + // clicking the Posts dock tile opens the `<os-table>`-driven
131 203 // native window instead of the chromeless `edit.php` iframe.
132 204 // Default OFF — the native windows are opt-in
133 205 // Beta. Fresh installs land on the classic iframe; users turn
134 206 // this on in OS Settings → Features → Beta features to try it.
@@ -135,9 +207,9 @@
135 207 // Per-user override of the WordPress Heartbeat interval, in
136 208 // seconds. 60s matches Core's "idle" default; the allowed
137 209 // rates (15/30/45/60) all sit at or above Core's 15 s
138 210 // `minimalInterval` floor. See
139 - // `desktop_mode_apply_heartbeat_rate_setting` for the
211 + // `openstation_apply_heartbeat_rate_setting` for the
140 212 // `heartbeat_settings` filter that applies this.
141 213 'heartbeatRate' => 60,
142 214 'nativePostsEnabled' => false,
143 215 // Per-user list of column keys hidden in the native Posts
@@ -144,8 +216,13 @@
144 216 // window (e.g. array( 'author', 'tags' )). Empty array means
145 217 // every column is visible. The sticky 'title' column is always
146 218 // shown — the UI prevents toggling it.
147 219 'nativePostsHiddenColumns' => array(),
220 + // Per-user list of column keys hidden in the native Pages
221 + // window (e.g. array( 'author', 'parent' )). Empty array means
222 + // every column is visible. The sticky 'title' column is always
223 + // shown — the UI prevents toggling it.
224 + 'nativePagesHiddenColumns' => array(),
148 225 // Per-user opt-IN for the native Pages window. Same posture as
149 226 // nativePostsEnabled — defaults OFF (Beta), users opt in to swap
150 227 // the classic `edit.php?post_type=page` iframe for the native UI.
151 228 'nativePagesEnabled' => false,
@@ -162,8 +239,17 @@
162 239 // Per-user opt-IN for the native Comments window. Defaults OFF
163 240 // (Beta); the server-side cap gate (`edit_posts`) means the
164 241 // toggle only matters for users who could see the Comments tile.
165 242 'nativeCommentsEnabled' => false,
243 + // Per-user opt-IN for Station Home, the native Dashboard
244 + // window. Defaults OFF: the ordinary `index.php` Dashboard
245 + // (including any custom dashboard a plugin builds there) opens
246 + // as a chromeless iframe until the user opts in via OS
247 + // Settings → Features → Beta features.
248 + 'stationHomeEnabled' => false,
249 + // Performance enhancements are enabled unless explicitly disabled.
250 + 'adminAssetCacheEnabled' => true,
251 + 'windowPrewarmEnabled' => true,
166 252 // When true, left-clicking the empty wallpaper triggers the
167 253 // "Show desktop" toggle (macOS-style) and the matching entry is
168 254 // hidden from the wallpaper context menu. When false (default),
169 255 // the entry stays in the menu and left clicks on the wallpaper
@@ -168,8 +254,31 @@
168 254 // hidden from the wallpaper context menu. When false (default),
169 255 // the entry stays in the menu and left clicks on the wallpaper
170 256 // do nothing. Per-user.
171 257 'showDesktopOnWallpaperClick' => false,
258 + // Whether the close-all-windows shortcut (Alt+Cmd/Ctrl+W) asks
259 + // before it closes. The dialog's "Don't ask again" checkbox is
260 + // what writes false; OpenStation Preferences -> Windows is what
261 + // turns it back on. Per-user.
262 + 'confirmCloseAllWindows' => true,
263 + // Mio — a soft-body companion that floats over
264 + // the wallpaper, settles onto nearby windows, and watches the
265 + // pointer. Off by default; toggled from the wallpaper context
266 + // menu. Per-user. See `docs/mio.md`.
267 + 'mioEnabled' => false,
268 + 'mioApiEnabled' => false,
269 + 'mioShowOnWallpaper' => true,
270 + // The user's own Mio, as built in "Make it yours": partial
271 + // appearance + silhouette overrides, both empty until they
272 + // touch a control. Stored per user rather than per browser
273 + // because it is a preference about the person — ten minutes
274 + // spent building a companion should be waiting on their phone.
275 + // Sanitized by `openstation_sanitize_mio_look()`; the ranges
276 + // are enforced client-side in `sanitizeMioConfig()`.
277 + 'mioStyle' => array(
278 + 'appearance' => array(),
279 + 'physics' => array(),
280 + ),
172 281 // Diagonal corner ribbon on My WordPress tiles whose post
173 282 // status isn't `publish` (draft / pending / private /
174 283 // scheduled). On by default — surfaces unpublished work at
175 284 // a glance. Per-user.
@@ -175,11 +284,12 @@
175 284 // a glance. Per-user.
176 285 'showPostStatusRibbons' => true,
177 286 // Unlocks developer-facing surfaces meant for plugin
178 287 // authors: the Starter Widget appears in the add-widget
179 - // picker, and the OS Settings → Components tab runs its
180 - // intentional missing-import-warner demo. Off by default.
181 - // Per-user.
288 + // picker, the OS Settings → Components tab runs its
289 + // intentional missing-import-warner demo, and the Code Blue
290 + // error-log reader registers (icon, window, REST routes).
291 + // Off by default. Per-user.
182 292 'developerModeEnabled' => false,
183 293 // Per-user opt-OUT for the folder-sharing feature. Defaults
184 294 // ON. When false:
185 295 // - The Share button, share-settings modal, "Leave shared
@@ -194,23 +304,30 @@
194 304 // disappears without any database changes. The site-wide
195 305 // "Delete folder sharing data" action in OS Settings →
196 306 // Features → Advanced is a separate destructive cleanup.
197 307 'foldersSharingEnabled' => true,
198 - // Per-item placement preferences. Map of item id (dock-item
199 - // slug or registered desktop-icon id) → one of:
200 - // 'both' — show on both dock and desktop.
201 - // 'dock' — show only on the dock; hide from desktop.
202 - // 'desktop' — show only on the wallpaper; hide from dock.
308 + // Per-item navigation placement. Map of item id → one of:
309 + // 'both' — show on a rail and on the desktop.
310 + // 'rail' — show only on a rail: the dock, or the sidebar
311 + // for a Core admin menu in the split layout.
312 + // 'desktop' — show only on the wallpaper.
203 313 // 'hidden' — hide from every shell surface.
204 - // Missing keys mean "no override" — items use their native rail.
314 + // Missing keys mean "no override" — the item takes the default
315 + // for its kind, which lives in `src/nav/defaults.ts`.
205 316 // Sanitized as map<sanitize_key, enum>. Capped at 256 entries.
206 - 'itemVisibility' => array(),
207 - // Per-user dock ordering. Ordered list of item ids; ids not in
208 - // the list keep their server-supplied position appended after
209 - // the listed ones. Unknown ids are tolerated.
210 - 'dockOrder' => array(),
211 - // Persisted desktop position for every dock item the user has
212 - // promoted to the wallpaper via `itemVisibility[id]=desktop|both`.
317 + 'navPlacement' => array(),
318 + // Per-user ordering, flat across every dock/sidebar zone. Ids
319 + // not in the list keep their registration order and render
320 + // after the listed ones. Unknown ids are tolerated.
321 + 'navOrder' => array(),
322 + // Which experience the shell renders: 'auto' follows the
323 + // viewport, 'desktop' / 'mobile' force it. See `includes/mobile.php`.
324 + 'mobileLayout' => 'auto',
325 + // Ids pinned to the phone tab bar, at most three. Empty means
326 + // the server default (`openstation_mobile_tab_bar`).
327 + 'mobileTabs' => array(),
328 + // Persisted desktop position for every item the user has
329 + // promoted to the wallpaper via `navPlacement[id]=desktop|both`.
213 330 // Keyed by item id, value is `{ x: int, y: int }`. The JS
214 331 // synthesizer reads this when building a synthetic placement so
215 332 // the icon lands where the user last dragged it instead of
216 333 // resetting to (0, 0) on every reload. Capped at 256 entries.
@@ -226,20 +343,20 @@
226 343 *
227 344 * @param int $user_id The user ID.
228 345 * @return array
229 346 */
230 -function desktop_mode_get_os_settings( $user_id ) {
347 +function openstation_get_os_settings( $user_id ) {
231 348 $user_id = (int) $user_id;
232 349 if ( $user_id <= 0 ) {
233 - return desktop_mode_default_os_settings();
350 + return openstation_sanitize_os_settings( array() );
234 351 }
235 352
236 - $raw = get_user_meta( $user_id, DESKTOP_MODE_OS_SETTINGS_META_KEY, true );
353 + $raw = get_user_meta( $user_id, OPENSTATION_OS_SETTINGS_META_KEY, true );
237 354 if ( ! is_array( $raw ) ) {
238 - return desktop_mode_default_os_settings();
355 + return openstation_sanitize_os_settings( array() );
239 356 }
240 357
241 - return desktop_mode_sanitize_os_settings( $raw );
358 + return openstation_sanitize_os_settings( $raw );
242 359 }
243 360
244 361 /**
245 362 * Saves sanitized OS settings for a user.
@@ -247,19 +364,87 @@
247 364 * @param int $user_id The user ID.
248 365 * @param mixed $settings Raw settings payload from the client.
249 366 * @return bool True on success, false otherwise.
250 367 */
251 -function desktop_mode_save_os_settings( $user_id, $settings ) {
368 +function openstation_save_os_settings( $user_id, $settings ) {
252 369 $user_id = (int) $user_id;
253 370 if ( $user_id <= 0 ) {
254 371 return false;
255 372 }
256 373
257 - $clean = desktop_mode_sanitize_os_settings( $settings );
258 - return false !== update_user_meta( $user_id, DESKTOP_MODE_OS_SETTINGS_META_KEY, $clean );
374 + $clean = openstation_sanitize_os_settings( $settings );
375 + return false !== update_user_meta( $user_id, OPENSTATION_OS_SETTINGS_META_KEY, $clean );
259 376 }
260 377
261 378 /**
379 + * Strip the rail-synthesis prefix an id could carry before the
380 + * navigation model.
381 + *
382 + * `dock:<id>` / `desktop:<id>` used to mean "this tile is a copy of an
383 + * item whose real home is the other rail". Nothing synthesizes copies
384 + * any more — an item is one item wherever it is painted — so the
385 + * prefix is noise, and left in place it would key a preference to an
386 + * id nothing registers.
387 + *
388 + * @param string $id Possibly-prefixed id.
389 + * @return string Canonical id.
390 + */
391 +function openstation_canonical_nav_id( $id ) {
392 + $id = (string) $id;
393 + if ( 0 === strpos( $id, 'dock:' ) ) {
394 + return substr( $id, 5 );
395 + }
396 + if ( 0 === strpos( $id, 'desktop:' ) ) {
397 + return substr( $id, 8 );
398 + }
399 + return $id;
400 +}
401 +
402 +/**
403 + * Carry a pre-navigation `itemVisibility` map into `navPlacement`.
404 + *
405 + * The only value that moves is `'dock'` → `'rail'`: the stored name
406 + * is now the REGION rather than a rail, so a Core admin menu the user
407 + * kept on a rail follows the layout into the sidebar instead of
408 + * needing a second migration the first time they switch.
409 + *
410 + * Runs on read (see {@see openstation_sanitize_os_settings()}) rather
411 + * than as a numbered migration, because OS settings are per-user meta
412 + * and a site with many users would pay for a sweep that the next save
413 + * performs for free.
414 + *
415 + * @param array $visibility Legacy map of item id → placement.
416 + * @return array Map of canonical item id → nav placement.
417 + */
418 +function openstation_migrate_item_visibility( $visibility ) {
419 + $map = array(
420 + 'dock' => 'rail',
421 + 'desktop' => 'desktop',
422 + 'both' => 'both',
423 + 'hidden' => 'hidden',
424 + );
425 +
426 + $out = array();
427 + foreach ( (array) $visibility as $key => $val ) {
428 + if ( ! is_string( $key ) || ! is_string( $val ) || ! isset( $map[ $val ] ) ) {
429 + continue;
430 + }
431 + $id = openstation_canonical_nav_id( $key );
432 + if ( '' === $id ) {
433 + continue;
434 + }
435 + // A prefixed and an unprefixed key can collapse onto the same
436 + // id. The unprefixed one is the item's own preference rather
437 + // than a synthesized copy's, so it wins whichever order they
438 + // arrive in.
439 + if ( $id === $key || ! isset( $out[ $id ] ) ) {
440 + $out[ $id ] = $map[ $val ];
441 + }
442 + }
443 + return $out;
444 +}
445 +
446 +/**
262 447 * Sanitizes a raw OS settings payload.
263 448 *
264 449 * Unknown keys are ignored; known keys are coerced field-by-field so a
265 450 * partial save (e.g., only accent changed) merges cleanly with the
@@ -267,20 +452,26 @@
267 452 *
268 453 * @param mixed $raw Raw settings from the client or user meta.
269 454 * @return array Sanitized settings.
270 455 */
271 -function desktop_mode_sanitize_os_settings( $raw ) {
272 - $defaults = desktop_mode_default_os_settings();
456 +function openstation_sanitize_os_settings( $raw ) {
457 + $defaults = openstation_default_os_settings();
273 458
274 459 if ( ! is_array( $raw ) ) {
275 - return $defaults;
460 + $raw = array();
276 461 }
277 462
278 463 // Wallpaper — any non-empty string; registry membership is validated
279 464 // client-side at apply time.
280 - $wallpaper = isset( $raw['wallpaper'] ) && is_string( $raw['wallpaper'] ) && '' !== $raw['wallpaper']
281 - ? sanitize_key( $raw['wallpaper'] )
282 - : $defaults['wallpaper'];
465 + // Key-clean per `/` segment: a desktop theme's wallpapers are
466 + // registered as `desktop-theme/<theme>/<id>`, and a flat
467 + // `sanitize_key()` would fold that into an id nothing answers to.
468 + $wallpaper = isset( $raw['wallpaper'] ) && is_string( $raw['wallpaper'] )
469 + ? implode( '/', array_filter( array_map( 'sanitize_key', explode( '/', $raw['wallpaper'] ) ) ) )
470 + : '';
471 + if ( '' === $wallpaper ) {
472 + $wallpaper = $defaults['wallpaper'];
473 + }
283 474
284 475 // Accent — non-empty string; swatch validity is enforced in the picker.
285 476 $accent = isset( $raw['accent'] ) && is_string( $raw['accent'] ) && '' !== $raw['accent']
286 477 ? sanitize_key( $raw['accent'] )
@@ -285,31 +476,64 @@
285 476 $accent = isset( $raw['accent'] ) && is_string( $raw['accent'] ) && '' !== $raw['accent']
286 477 ? sanitize_key( $raw['accent'] )
287 478 : $defaults['accent'];
288 479
480 + // The colour behind the Custom swatch. A full `#rrggbb` triplet and
481 + // nothing else: `sanitize_hex_color()` would also pass `#abc`, which
482 + // the client-side parser rejects, and a value that survives the save
483 + // only to be dropped on load is worse than one refused here.
484 + $custom_accent = isset( $raw['customAccent'] )
485 + && is_string( $raw['customAccent'] )
486 + && preg_match( '/^#[0-9a-fA-F]{6}$/', $raw['customAccent'] )
487 + ? strtolower( $raw['customAccent'] )
488 + : $defaults['customAccent'];
489 +
289 490 // Dock size — must be one of the three known values.
290 - $dock_size = isset( $raw['dockSize'] ) && in_array( $raw['dockSize'], DESKTOP_MODE_OS_SETTINGS_DOCK_SIZES, true )
491 + $dock_size = isset( $raw['dockSize'] ) && in_array( $raw['dockSize'], OPENSTATION_OS_SETTINGS_DOCK_SIZES, true )
291 492 ? (string) $raw['dockSize']
292 493 : $defaults['dockSize'];
293 494
294 495 // Window radius — must be one of the three known values.
295 - $window_radius = isset( $raw['windowRadius'] ) && in_array( $raw['windowRadius'], DESKTOP_MODE_OS_SETTINGS_WINDOW_RADII, true )
496 + $window_radius = isset( $raw['windowRadius'] ) && in_array( $raw['windowRadius'], OPENSTATION_OS_SETTINGS_WINDOW_RADII, true )
296 497 ? (string) $raw['windowRadius']
297 498 : $defaults['windowRadius'];
298 499
500 + // How a newly opened window lands — one of the three known values.
501 + $open_windows_as = isset( $raw['openWindowsAs'] ) && in_array( $raw['openWindowsAs'], OPENSTATION_OS_SETTINGS_OPEN_WINDOWS_AS, true )
502 + ? (string) $raw['openWindowsAs']
503 + : $defaults['openWindowsAs'];
504 +
299 505 // Admin-bar mode — must be one of the three known values.
300 506 $admin_bar_mode = isset( $raw['adminBarMode'] )
301 - && in_array( $raw['adminBarMode'], DESKTOP_MODE_OS_SETTINGS_ADMIN_BAR_MODES, true )
507 + && in_array( $raw['adminBarMode'], OPENSTATION_OS_SETTINGS_ADMIN_BAR_MODES, true )
302 508 ? (string) $raw['adminBarMode']
303 509 : $defaults['adminBarMode'];
304 510
305 - // Desktop layout — must be one of the three known values
306 - // (`classic`, `unified`, `spatial`). Default `classic`.
511 + // Dock behavior — must be one of the two known values. One answer
512 + // per rail: the dock, and the Split layout's sidebar.
513 + $dock_behavior = isset( $raw['dockBehavior'] )
514 + && in_array( $raw['dockBehavior'], OPENSTATION_OS_SETTINGS_DOCK_BEHAVIORS, true )
515 + ? (string) $raw['dockBehavior']
516 + : $defaults['dockBehavior'];
517 + $side_dock_behavior = isset( $raw['sideDockBehavior'] )
518 + && in_array( $raw['sideDockBehavior'], OPENSTATION_OS_SETTINGS_DOCK_BEHAVIORS, true )
519 + ? (string) $raw['sideDockBehavior']
520 + : $defaults['sideDockBehavior'];
521 +
522 + // Desktop layout — must be one of the known values (`classic`,
523 + // `unified`). Default `unified`.
307 524 $desktop_layout = isset( $raw['desktopLayout'] )
308 - && in_array( $raw['desktopLayout'], DESKTOP_MODE_OS_SETTINGS_DESKTOP_LAYOUTS, true )
525 + && in_array( $raw['desktopLayout'], OPENSTATION_OS_SETTINGS_DESKTOP_LAYOUTS, true )
309 526 ? (string) $raw['desktopLayout']
310 527 : $defaults['desktopLayout'];
311 528
529 + // Dock placement — which edge the single dock sits on. Must be one
530 + // of the three known values (`bottom`, `left`, `right`).
531 + $dock_placement = isset( $raw['dockPlacement'] )
532 + && in_array( $raw['dockPlacement'], OPENSTATION_OS_SETTINGS_DOCK_PLACEMENTS, true )
533 + ? (string) $raw['dockPlacement']
534 + : $defaults['dockPlacement'];
535 +
312 536 // Dock rail renderer id — accept any sanitize_key()-clean
313 537 // string. JS-side registry resolves at use time and falls back
314 538 // to `'default'` when the picked renderer isn't registered.
315 539 $dock_rail_renderer = $defaults['dockRailRenderer'];
@@ -395,10 +619,10 @@
395 619 if ( isset( $raw['windowRevealDuration'] ) && is_numeric( $raw['windowRevealDuration'] ) ) {
396 620 $requested = (int) round( (float) $raw['windowRevealDuration'] );
397 621 if ( $requested > 0 ) {
398 622 $window_reveal_duration = max(
399 - DESKTOP_MODE_OS_SETTINGS_REVEAL_DURATION_MIN,
400 - min( DESKTOP_MODE_OS_SETTINGS_REVEAL_DURATION_MAX, $requested )
623 + OPENSTATION_OS_SETTINGS_REVEAL_DURATION_MIN,
624 + min( OPENSTATION_OS_SETTINGS_REVEAL_DURATION_MAX, $requested )
401 625 );
402 626 } else {
403 627 $window_reveal_duration = 0;
404 628 }
@@ -537,9 +761,9 @@
537 761 }
538 762 }
539 763
540 764 // Heartbeat rate — one of the four allowed values. The PHP
541 - // filter `desktop_mode_apply_heartbeat_rate_setting` reads
765 + // filter `openstation_apply_heartbeat_rate_setting` reads
542 766 // this and passes it through to `heartbeat_settings` so
543 767 // WordPress Core itself reduces the interval on the next page
544 768 // load. 5 s is intentionally excluded: Core's
545 769 // `minimalInterval` floor clamps anything below 15 back up to
@@ -576,8 +800,27 @@
576 800 // user meta indefinitely.
577 801 $native_posts_hidden_columns = array_slice( array_values( array_unique( $native_posts_hidden_columns ) ), 0, 32 );
578 802 }
579 803
804 + $native_pages_hidden_columns = $defaults['nativePagesHiddenColumns'];
805 + if ( isset( $raw['nativePagesHiddenColumns'] ) && is_array( $raw['nativePagesHiddenColumns'] ) ) {
806 + $native_pages_hidden_columns = array();
807 + foreach ( $raw['nativePagesHiddenColumns'] as $col ) {
808 + if ( ! is_string( $col ) || '' === $col ) {
809 + continue;
810 + }
811 + $slug = sanitize_key( $col );
812 + if ( '' === $slug ) {
813 + continue;
814 + }
815 + $native_pages_hidden_columns[] = $slug;
816 + }
817 + // Cap to a sane upper bound — far more than any plausible
818 + // column count, but blocks a malicious payload from bloating
819 + // user meta indefinitely.
820 + $native_pages_hidden_columns = array_slice( array_values( array_unique( $native_pages_hidden_columns ) ), 0, 32 );
821 + }
822 +
580 823 $native_pages_enabled = isset( $raw['nativePagesEnabled'] )
581 824 ? (bool) $raw['nativePagesEnabled']
582 825 : $defaults['nativePagesEnabled'];
583 826
@@ -592,12 +835,37 @@
592 835 $native_comments_enabled = isset( $raw['nativeCommentsEnabled'] )
593 836 ? (bool) $raw['nativeCommentsEnabled']
594 837 : $defaults['nativeCommentsEnabled'];
595 838
839 + $station_home_enabled = isset( $raw['stationHomeEnabled'] )
840 + ? (bool) $raw['stationHomeEnabled']
841 + : $defaults['stationHomeEnabled'];
842 +
843 + // Site-wide performance controls supersede the legacy per-user values.
844 + // Keep the snapshot keys so existing window consumers can read them.
845 + $extended_options = openstation_get_extended_options();
846 + $admin_asset_cache_enabled = $extended_options['admin_asset_cache'];
847 + $window_prewarm_enabled = $extended_options['window_prewarm'];
848 +
596 849 $show_desktop_on_wallpaper_click = isset( $raw['showDesktopOnWallpaperClick'] )
597 850 ? (bool) $raw['showDesktopOnWallpaperClick']
598 851 : $defaults['showDesktopOnWallpaperClick'];
599 852
853 + $confirm_close_all_windows = isset( $raw['confirmCloseAllWindows'] )
854 + ? (bool) $raw['confirmCloseAllWindows']
855 + : $defaults['confirmCloseAllWindows'];
856 +
857 + $mio_enabled = isset( $raw['mioEnabled'] )
858 + ? (bool) $raw['mioEnabled']
859 + : ( isset( $raw['mioApiEnabled'] ) ? (bool) $raw['mioApiEnabled'] : $defaults['mioEnabled'] );
860 +
861 + // A missing key means "no look saved yet", which sanitizes to the
862 + // same pair of empty arrays the defaults carry — so this needs no
863 + // isset() branch of its own.
864 + $mio_style = openstation_sanitize_mio_look(
865 + isset( $raw['mioStyle'] ) ? $raw['mioStyle'] : null
866 + );
867 +
600 868 $show_post_status_ribbons = isset( $raw['showPostStatusRibbons'] )
601 869 ? (bool) $raw['showPostStatusRibbons']
602 870 : $defaults['showPostStatusRibbons'];
603 871
@@ -604,20 +872,59 @@
604 872 $developer_mode_enabled = isset( $raw['developerModeEnabled'] )
605 873 ? (bool) $raw['developerModeEnabled']
606 874 : $defaults['developerModeEnabled'];
607 875
876 + // mobileLayout — the phone/desktop override.
877 + $mobile_layout = isset( $raw['mobileLayout'] )
878 + && in_array( $raw['mobileLayout'], OPENSTATION_OS_SETTINGS_MOBILE_LAYOUTS, true )
879 + ? (string) $raw['mobileLayout']
880 + : $defaults['mobileLayout'];
881 +
882 + // mobileTabs — ordered nav ids pinned to the phone tab bar. Same
883 + // id grammar as navOrder, capped at the tab bar's slot count.
884 + $mobile_tabs = array();
885 + if ( isset( $raw['mobileTabs'] ) && is_array( $raw['mobileTabs'] ) ) {
886 + $seen_tabs = array();
887 + foreach ( $raw['mobileTabs'] as $id ) {
888 + if ( ! is_string( $id ) || '' === $id ) {
889 + continue;
890 + }
891 + $slug = sanitize_key( openstation_canonical_nav_id( $id ) );
892 + if ( '' === $slug || isset( $seen_tabs[ $slug ] ) ) {
893 + continue;
894 + }
895 + $seen_tabs[ $slug ] = true;
896 + $mobile_tabs[] = $slug;
897 + if ( count( $mobile_tabs ) >= OPENSTATION_OS_SETTINGS_MOBILE_TABS_MAX ) {
898 + break;
899 + }
900 + }
901 + }
902 +
608 903 $folders_sharing_enabled = isset( $raw['foldersSharingEnabled'] )
609 904 ? (bool) $raw['foldersSharingEnabled']
610 905 : $defaults['foldersSharingEnabled'];
611 906
612 - // itemVisibility — map<sanitize_key, enum>. Unknown ids are kept
907 + // navPlacement — map<sanitize_key, enum>. Unknown ids are kept
613 908 // (a deactivated plugin's setting should survive reactivation);
614 909 // invalid placement values are dropped.
615 - $item_visibility = array();
616 - if ( isset( $raw['itemVisibility'] ) && is_array( $raw['itemVisibility'] ) ) {
617 - $allowed_placements = array( 'both', 'dock', 'desktop', 'hidden' );
910 + //
911 + // Reads the pre-navigation `itemVisibility` map when this user has
912 + // no `navPlacement` yet, so an existing arrangement carries over on
913 + // first load and is written back on the next save. See
914 + // `openstation_migrate_item_visibility()`.
915 + $raw_placement = array();
916 + if ( isset( $raw['navPlacement'] ) && is_array( $raw['navPlacement'] ) ) {
917 + $raw_placement = $raw['navPlacement'];
918 + } elseif ( isset( $raw['itemVisibility'] ) && is_array( $raw['itemVisibility'] ) ) {
919 + $raw_placement = openstation_migrate_item_visibility( $raw['itemVisibility'] );
920 + }
921 +
922 + $nav_placement = array();
923 + if ( ! empty( $raw_placement ) ) {
924 + $allowed_placements = array( 'both', 'rail', 'desktop', 'hidden' );
618 925 $count = 0;
619 - foreach ( $raw['itemVisibility'] as $key => $val ) {
926 + foreach ( $raw_placement as $key => $val ) {
620 927 if ( $count >= 256 ) {
621 928 break;
622 929 }
623 930 if ( ! is_string( $key ) || '' === $key || ! is_string( $val ) ) {
@@ -629,34 +936,38 @@
629 936 }
630 937 if ( ! in_array( $val, $allowed_placements, true ) ) {
631 938 continue;
632 939 }
633 - $item_visibility[ $slug ] = $val;
940 + $nav_placement[ $slug ] = $val;
634 941 ++$count;
635 942 }
636 943 }
637 944
638 - // dockOrder — ordered list of item ids. Most are sanitize_key()-
639 - // clean dock slugs, but cross-rail tiles the user promoted carry a
640 - // rail-synthesis prefix (`desktop:<id>` / `dock:<id>`, built by
641 - // src/settings/item-placement.ts). sanitize_key() strips the colon,
642 - // which silently breaks the JS order match on reload and can collide
643 - // with an unrelated id — so allow the colon (and hyphen/underscore)
644 - // while still rejecting anything outside the JS id charset.
645 - $dock_order = array();
646 - if ( isset( $raw['dockOrder'] ) && is_array( $raw['dockOrder'] ) ) {
945 + // navOrder — ordered list of item ids, flat across every zone.
946 + // Reads the pre-navigation `dockOrder` when absent, stripping the
947 + // rail-synthesis prefixes (`dock:` / `desktop:`) that model no
948 + // longer has.
949 + $raw_order = array();
950 + if ( isset( $raw['navOrder'] ) && is_array( $raw['navOrder'] ) ) {
951 + $raw_order = $raw['navOrder'];
952 + } elseif ( isset( $raw['dockOrder'] ) && is_array( $raw['dockOrder'] ) ) {
953 + $raw_order = $raw['dockOrder'];
954 + }
955 +
956 + $nav_order = array();
957 + if ( ! empty( $raw_order ) ) {
647 958 $seen = array();
648 - foreach ( $raw['dockOrder'] as $id ) {
959 + foreach ( $raw_order as $id ) {
649 960 if ( ! is_string( $id ) || '' === $id ) {
650 961 continue;
651 962 }
652 - $slug = (string) preg_replace( '/[^a-z0-9_:-]+/', '', strtolower( $id ) );
963 + $slug = sanitize_key( openstation_canonical_nav_id( $id ) );
653 964 if ( '' === $slug || isset( $seen[ $slug ] ) ) {
654 965 continue;
655 966 }
656 967 $seen[ $slug ] = true;
657 - $dock_order[] = $slug;
658 - if ( count( $dock_order ) >= 256 ) {
968 + $nav_order[] = $slug;
969 + if ( count( $nav_order ) >= 256 ) {
659 970 break;
660 971 }
661 972 }
662 973 }
@@ -704,12 +1015,17 @@
704 1015
705 1016 return array(
706 1017 'wallpaper' => $wallpaper,
707 1018 'accent' => $accent,
1019 + 'customAccent' => $custom_accent,
708 1020 'dockSize' => $dock_size,
709 1021 'windowRadius' => $window_radius,
1022 + 'openWindowsAs' => $open_windows_as,
710 1023 'adminBarMode' => $admin_bar_mode,
711 1024 'desktopLayout' => $desktop_layout,
1025 + 'dockPlacement' => $dock_placement,
1026 + 'dockBehavior' => $dock_behavior,
1027 + 'sideDockBehavior' => $side_dock_behavior,
712 1028 'dockRailRenderer' => $dock_rail_renderer,
713 1029 'desktopTheme' => $desktop_theme,
714 1030 'appliedThemeRecommendations' => $applied_theme_recommendations,
715 1031 'unfocusEffect' => $unfocus_effect,
@@ -728,17 +1044,28 @@
728 1044 'heartbeatRate' => $heartbeat_rate,
729 1045 'nativePostsEnabled' => $native_posts_enabled,
730 1046 'nativePostsHiddenColumns' => $native_posts_hidden_columns,
731 1047 'nativePagesEnabled' => $native_pages_enabled,
1048 + 'nativePagesHiddenColumns' => $native_pages_hidden_columns,
732 1049 'nativeUsersEnabled' => $native_users_enabled,
733 1050 'nativePluginsEnabled' => $native_plugins_enabled,
734 1051 'nativeCommentsEnabled' => $native_comments_enabled,
1052 + 'stationHomeEnabled' => $station_home_enabled,
1053 + 'adminAssetCacheEnabled' => $admin_asset_cache_enabled,
1054 + 'windowPrewarmEnabled' => $window_prewarm_enabled,
735 1055 'showDesktopOnWallpaperClick' => $show_desktop_on_wallpaper_click,
1056 + 'confirmCloseAllWindows' => $confirm_close_all_windows,
1057 + 'mioEnabled' => $mio_enabled,
1058 + 'mioApiEnabled' => $mio_enabled,
1059 + 'mioShowOnWallpaper' => isset( $raw['mioShowOnWallpaper'] ) ? (bool) $raw['mioShowOnWallpaper'] : $defaults['mioShowOnWallpaper'],
1060 + 'mioStyle' => $mio_style,
736 1061 'showPostStatusRibbons' => $show_post_status_ribbons,
737 1062 'developerModeEnabled' => $developer_mode_enabled,
738 1063 'foldersSharingEnabled' => $folders_sharing_enabled,
739 - 'itemVisibility' => $item_visibility,
740 - 'dockOrder' => $dock_order,
1064 + 'navPlacement' => $nav_placement,
1065 + 'navOrder' => $nav_order,
1066 + 'mobileLayout' => $mobile_layout,
1067 + 'mobileTabs' => $mobile_tabs,
741 1068 'dockPromotedPositions' => $dock_promoted_positions,
742 1069 );
743 1070 }
744 1071
@@ -744,9 +1071,9 @@
744 1071
745 1072 /**
746 1073 * Registers the REST routes for OS settings.
747 1074 */
748 -function desktop_mode_register_os_settings_rest_routes() {
1075 +function openstation_register_os_settings_rest_routes() {
749 1076 register_rest_route(
750 1077 'desktop-mode/v1',
751 1078 '/os-settings',
752 1079 array(
@@ -751,15 +1078,15 @@
751 1078 '/os-settings',
752 1079 array(
753 1080 array(
754 1081 'methods' => WP_REST_Server::READABLE,
755 - 'callback' => 'desktop_mode_rest_get_os_settings',
756 - 'permission_callback' => 'desktop_mode_rest_os_settings_permission',
1082 + 'callback' => 'openstation_rest_get_os_settings',
1083 + 'permission_callback' => 'openstation_rest_os_settings_permission',
757 1084 ),
758 1085 array(
759 1086 'methods' => WP_REST_Server::CREATABLE,
760 - 'callback' => 'desktop_mode_rest_save_os_settings',
761 - 'permission_callback' => 'desktop_mode_rest_os_settings_permission',
1087 + 'callback' => 'openstation_rest_save_os_settings',
1088 + 'permission_callback' => 'openstation_rest_os_settings_permission',
762 1089 'args' => array(
763 1090 'settings' => array(
764 1091 'required' => true,
765 1092 'type' => 'object',
@@ -768,21 +1095,21 @@
768 1095 ),
769 1096 )
770 1097 );
771 1098 }
772 -add_action( 'rest_api_init', 'desktop_mode_register_os_settings_rest_routes' );
1099 +add_action( 'rest_api_init', 'openstation_register_os_settings_rest_routes' );
773 1100
774 1101 /**
775 1102 * Permission gate for OS settings REST routes.
776 1103 *
777 - * Requires the caller to be logged in *and* have desktop mode enabled —
778 - * see {@see desktop_mode_rest_require_enabled()} for why `read` alone is
1104 + * Requires the caller to be logged in *and* have OpenStation enabled —
1105 + * see {@see openstation_rest_require_enabled()} for why `read` alone is
779 1106 * insufficient.
780 1107 *
781 1108 * @return true|WP_Error
782 1109 */
783 -function desktop_mode_rest_os_settings_permission() {
784 - return desktop_mode_rest_require_enabled();
1110 +function openstation_rest_os_settings_permission() {
1111 + return openstation_rest_require_enabled();
785 1112 }
786 1113
787 1114 /**
788 1115 * GET /desktop-mode/v1/os-settings
@@ -788,23 +1115,70 @@
788 1115 * GET /desktop-mode/v1/os-settings
789 1116 *
790 1117 * @return WP_REST_Response
791 1118 */
792 -function desktop_mode_rest_get_os_settings() {
793 - return rest_ensure_response( desktop_mode_get_os_settings( get_current_user_id() ) );
1119 +function openstation_rest_get_os_settings() {
1120 + return rest_ensure_response( openstation_get_os_settings( get_current_user_id() ) );
794 1121 }
795 1122
796 1123 /**
797 1124 * POST /desktop-mode/v1/os-settings
798 1125 *
1126 + * Accepts a PARTIAL payload: keys the request omits keep the value
1127 + * already stored for the user, rather than resetting to the shipped
1128 + * default. The client sends only the fields that changed since its
1129 + * last confirmed save, which is what stops two open sessions from
1130 + * overwriting each other — a session that never touched the
1131 + * wallpaper cannot express an opinion about it, so a stale snapshot
1132 + * can no longer undo another session's unrelated change.
1133 + *
1134 + * A full payload still behaves exactly as before: every key is
1135 + * present, so every key wins.
1136 + *
1137 + * The merge lives here rather than in {@see openstation_save_os_settings()}
1138 + * on purpose. That function's contract is REPLACE, and migrations
1139 + * depend on it: migration 1 in `includes/migrations.php` `unset()`s
1140 + * keys and re-saves precisely so the sanitizer backfills the new
1141 + * defaults. Give the saver merge semantics and that migration
1142 + * silently becomes a no-op.
1143 + *
1144 + * Merging is shallow, one level deep. For the map-shaped fields
1145 + * (`wallpaperSettings`, `navPlacement`, `navOrder`,
1146 + * `dockPromotedPositions`) a request that sends the key replaces the
1147 + * whole map — deep-merging them would leave no way to delete an
1148 + * entry.
1149 + *
799 1150 * @param WP_REST_Request $request The REST request.
800 1151 * @return WP_REST_Response The saved settings (after sanitization).
801 1152 */
802 -function desktop_mode_rest_save_os_settings( WP_REST_Request $request ) {
1153 +function openstation_rest_save_os_settings( WP_REST_Request $request ) {
803 1154 $user_id = get_current_user_id();
804 1155 $payload = $request->get_param( 'settings' );
805 - desktop_mode_save_os_settings( $user_id, $payload );
806 - return rest_ensure_response( desktop_mode_get_os_settings( $user_id ) );
1156 +
1157 + // A payload that isn't an object says nothing about any field, so
1158 + // it changes nothing. The route declares `'settings' => object`
1159 + // and WP's schema validation rejects a scalar before the callback
1160 + // runs, so this is unreachable over real REST traffic — but the
1161 + // sanitizer resolves a non-array to the full defaults, which
1162 + // means the one way to reach this function with a bad payload
1163 + // used to be the one way to wipe a user's settings. Returning
1164 + // early costs nothing and keeps "don't destroy what wasn't sent"
1165 + // true of every path into this handler, not just the ones the
1166 + // schema happens to guard.
1167 + if ( ! is_array( $payload ) ) {
1168 + return rest_ensure_response( openstation_get_os_settings( $user_id ) );
1169 + }
1170 +
1171 + // Normalize an alias-only patch before merging it with the saved master value.
1172 + if ( ! array_key_exists( 'mioEnabled', $payload ) && array_key_exists( 'mioApiEnabled', $payload ) ) {
1173 + $payload['mioEnabled'] = $payload['mioApiEnabled'];
1174 + }
1175 +
1176 + openstation_save_os_settings(
1177 + $user_id,
1178 + array_merge( openstation_get_os_settings( $user_id ), $payload )
1179 + );
1180 + return rest_ensure_response( openstation_get_os_settings( $user_id ) );
807 1181 }
808 1182
809 1183 /**
810 1184 * Apply the per-user Heartbeat-rate preference to the
@@ -812,15 +1186,15 @@
812 1186 * once at page load. We set `interval` only; the allowed rates
813 1187 * (15/30/45/60 s) all sit at or above Core's 15 s
814 1188 * `minimalInterval` floor, so the floor never needs overriding.
815 1189 *
816 - * Only applies to users with Desktop Mode enabled — non-desktop
1190 + * Only applies to users with OpenStation enabled — non-desktop
817 1191 * sessions keep Core's defaults. Anonymous requests skip too.
818 1192 *
819 1193 * @param array $settings Filtered Heartbeat settings.
820 1194 * @return array
821 1195 */
822 -function desktop_mode_apply_heartbeat_rate_setting( $settings ) {
1196 +function openstation_apply_heartbeat_rate_setting( $settings ) {
823 1197 if ( ! is_array( $settings ) ) {
824 1198 $settings = array();
825 1199 }
826 1200 $user_id = get_current_user_id();
@@ -826,12 +1200,12 @@
826 1200 $user_id = get_current_user_id();
827 1201 if ( $user_id <= 0 ) {
828 1202 return $settings;
829 1203 }
830 - if ( function_exists( 'desktop_mode_is_enabled' ) && ! desktop_mode_is_enabled( $user_id ) ) {
1204 + if ( function_exists( 'openstation_is_enabled' ) && ! openstation_is_enabled( $user_id ) ) {
831 1205 return $settings;
832 1206 }
833 - $os = desktop_mode_get_os_settings( $user_id );
1207 + $os = openstation_get_os_settings( $user_id );
834 1208 $rate = isset( $os['heartbeatRate'] ) ? (int) $os['heartbeatRate'] : 0;
835 1209 if ( ! in_array( $rate, array( 15, 30, 45, 60 ), true ) ) {
836 1210 return $settings;
837 1211 }
@@ -837,5 +1211,5 @@
837 1211 }
838 1212 $settings['interval'] = $rate;
839 1213 return $settings;
840 1214 }
841 -add_filter( 'heartbeat_settings', 'desktop_mode_apply_heartbeat_rate_setting' );
1215 +add_filter( 'heartbeat_settings', 'openstation_apply_heartbeat_rate_setting' );