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

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

256 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode helper functions.
4 *
5 * @package WPDesktopMode
6 */
7
8 defined( 'ABSPATH' ) || exit;
9
10 /**
11 * Filename suffix for built JS/CSS bundles: `.min` in production,
12 * `''` (the unminified dev build) under SCRIPT_DEBUG.
13 *
14 * Centralised because the SCRIPT_DEBUG branch needs a guard the old
15 * per-file ternaries didn't have: release zips ship the minified
16 * bundles only (the ~4–5 MB of dev bundles are a source-checkout
17 * artifact — see bin/package.sh), so a production site that happens
18 * to define SCRIPT_DEBUG would otherwise request dev files that
19 * don't exist and 404 every desktop-mode script. Probe one
20 * canonical dev bundle; if it's absent, this is a minified-only
21 * install and `.min` is the only truth available.
22 *
23 * @return string `'.min'` or `''`.
24 */
25 function desktop_mode_asset_suffix() {
26 static $suffix = null;
27 if ( null !== $suffix ) {
28 return $suffix;
29 }
30 if ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ) {
31 $suffix = '.min';
32 return $suffix;
33 }
34 $suffix = file_exists( DESKTOP_MODE_DIR . 'assets/js/desktop.js' ) ? '' : '.min';
35 return $suffix;
36 }
37
38 /**
39 * Checks whether a user has desktop mode enabled.
40 *
41 * Two gates, both must pass:
42 *
43 * 1. The user's `desktop_mode_mode` user-meta is `'1'` (the per-user
44 * opt-in toggle the admin-bar button writes via the AJAX endpoint).
45 * 2. The `desktop_mode_mode_enabled` filter returns truthy for that user.
46 *
47 * Centralising the filter check here means render-time gates (chromeless
48 * detection, payload generation, REST permission callbacks) can rely on
49 * a single helper instead of every call site re-running the filter.
50 * A user whose meta is `'1'` but whose filter denies them is treated as
51 * not-enabled everywhere, which is the documented contract of the
52 * filter — see docs/examples/gate-by-role.md.
53 *
54 * @param int $user_id Optional. User ID to check. Defaults to the
55 * current user.
56 * @return bool True if the user has desktop mode active.
57 */
58 function desktop_mode_is_enabled( $user_id = 0 ) {
59 $user_id = (int) $user_id;
60 if ( $user_id <= 0 ) {
61 if ( ! is_user_logged_in() ) {
62 return false;
63 }
64 $user_id = get_current_user_id();
65 }
66
67 if ( '1' !== (string) get_user_meta( $user_id, 'desktop_mode_mode', true ) ) {
68 return false;
69 }
70
71 /**
72 * Filters whether desktop mode is available for this user.
73 *
74 * See `docs/hooks-reference.md` (`desktop_mode_mode_enabled`) for the
75 * full contract. Returning `false` here makes the helper return
76 * `false` for the user even when their meta is set, which propagates
77 * to every render-time gate that consults the helper.
78 *
79 * @param bool $enabled Whether desktop mode is enabled. Default true.
80 * @param int $user_id The user ID being checked.
81 */
82 return (bool) apply_filters( 'desktop_mode_mode_enabled', true, $user_id );
83 }
84
85 /**
86 * Shared REST permission gate for Desktop Mode's per-user endpoints.
87 *
88 * Routes that only ever read or write the *current* user's own Desktop
89 * Mode state (OS settings, session, default-window, seen-intros, PWA
90 * state, presence) must not be reachable by accounts that haven't
91 * actually entered Desktop Mode.
92 *
93 * `current_user_can( 'read' )` alone is too loose: every authenticated
94 * role — Subscriber included — carries `read`, so the old gate let any
95 * logged-in user touch these routes without ever enabling Desktop Mode.
96 * We gate on {@see desktop_mode_is_enabled()} instead (the same opt-in +
97 * `desktop_mode_mode_enabled` filter the shell itself uses) and return
98 * the conventional 401/403 split so REST clients can tell "log in" from
99 * "not allowed".
100 *
101 * This is the canonical gate; `desktop_mode_presence_rest_permission()`
102 * pioneered the shape and now delegates here.
103 *
104 * @return true|WP_Error True when allowed; a `rest_forbidden` WP_Error
105 * (401 when logged out, 403 when desktop mode is
106 * not enabled for the account) otherwise.
107 */
108 function desktop_mode_rest_require_enabled() {
109 if ( ! is_user_logged_in() ) {
110 return new WP_Error(
111 'rest_forbidden',
112 __( 'Authentication required.', 'desktop-mode' ),
113 array( 'status' => 401 )
114 );
115 }
116
117 if ( ! desktop_mode_is_enabled() ) {
118 return new WP_Error(
119 'rest_forbidden',
120 __( 'Desktop mode is not enabled for your account.', 'desktop-mode' ),
121 array( 'status' => 403 )
122 );
123 }
124
125 return true;
126 }
127
128 // Chromeless / classic admin-bar suppression and the `wp_redirect`
129 // flag-preservation filter pair were moved to
130 // `includes/core/routing.php`. The functions and the
131 // add_filter / add_action hookings live there now; this file
132 // remains the home of `desktop_mode_is_enabled()` (called from the
133 // routing helpers at hook-fire time, after every include has
134 // loaded), which is why `desktop-mode.php` can safely require
135 // routing.php BEFORE helpers.php.
136
137 /**
138 * `desktop_mode_is_chromeless_request()` and `desktop_mode_is_classic_request()`
139 * were moved to `includes/core/routing.php` — see that
140 * file for the canonical definitions. The function names didn't
141 * change; PHP looks them up by name at call time, so every
142 * existing caller (helpers, render, hooks) keeps working.
143 */
144
145 /**
146 * Returns the default wallpaper id used when a user has no saved
147 * selection (or their saved selection was unregistered by a plugin
148 * deactivation).
149 *
150 * Exposed as a filter so themes/plugins can set a site-wide default
151 * without forking the TS build.
152 *
153 * ```php
154 * add_filter( 'desktop_mode_default_wallpaper', function () {
155 * return 'my-plugin/brand';
156 * } );
157 * ```
158 *
159 * The returned string is passed through `sanitize_key()` so a filter
160 * that returns an invalid slug degrades to the empty string (and the
161 * shell falls back to its hard-coded `'dark'` preset).
162 *
163 * @return string Wallpaper id. Empty string if the filter returns
164 * an invalid value.
165 */
166 function desktop_mode_get_default_wallpaper() {
167 /**
168 * Filters the wallpaper id loaded on first boot / new user.
169 *
170 * @param string $id Default wallpaper slug.
171 */
172 $id = apply_filters( 'desktop_mode_default_wallpaper', 'dark' );
173 if ( ! is_string( $id ) ) {
174 return '';
175 }
176 return sanitize_key( $id );
177 }
178
179 /**
180 * The site's own name, ready to use as a window / icon title.
181 *
182 * The desktop shows objects, not the software running it — so the
183 * folder that holds a site's content is titled after the site itself
184 * ("Izzi's Gym"), not after WordPress. This is the single source for
185 * that string.
186 *
187 * `get_bloginfo( 'name' )` returns the display-filtered option, which
188 * carries HTML entities (`&amp;`, `&#039;`). Titles land in
189 * `title=` attributes and JS-rendered text nodes, so the entities are
190 * decoded here — leaving them encoded would render a literal
191 * `Ben &amp; Jerry` on the desktop.
192 *
193 * @return string Decoded site title. Falls back to `WordPress` when
194 * the site has no name set.
195 */
196 function desktop_mode_site_title() {
197 $title = wp_specialchars_decode( (string) get_bloginfo( 'name' ), ENT_QUOTES );
198 $title = trim( $title );
199
200 if ( '' === $title ) {
201 $title = __( 'WordPress', 'desktop-mode' );
202 }
203
204 /**
205 * Filters the site title used for desktop-mode window and icon
206 * titles — the pinned site folder, its breadcrumb root, and any
207 * "Open in <site>" action.
208 *
209 * Return a different string to label the desktop objects after
210 * something other than `blogname` (a brand, a network name, a
211 * per-user workspace label).
212 *
213 * @param string $title Decoded site title, never empty.
214 */
215 $filtered = apply_filters( 'desktop_mode_site_title', $title );
216
217 return is_string( $filtered ) && '' !== trim( $filtered ) ? $filtered : $title;
218 }
219
220 /**
221 * Build a `WP_Error` for a desktop-mode registration failure.
222 *
223 * Centralises the error-code vocabulary used by every
224 * `desktop_mode_register_*()` function so plugin authors see a
225 * consistent contract. The canonical error-code list lives in
226 * `docs/hooks-reference.md`.
227 *
228 * @param string $code Short error slug (e.g. `desktop_mode_missing_title`).
229 * @param string $message Human-readable message. Should be translated.
230 * @param array $data Optional extra context attached to the error.
231 * @return WP_Error
232 */
233 function desktop_mode_registration_error( $code, $message, $data = array() ) {
234 return new WP_Error(
235 (string) $code,
236 (string) $message,
237 is_array( $data ) ? $data : array()
238 );
239 }
240
241 // `desktop_mode_url_is_same_admin()`,
242 // `desktop_mode_resolve_admin_target()` and
243 // `desktop_mode_admin_target_allowlist()` were moved to
244 // `includes/core/routing.php` — see that file for the
245 // canonical definitions. Function names didn't change; PHP's
246 // runtime resolution finds them across the module split.
247
248
249 // Dock building, menu / native-windows payload assembly and the
250 // script/style handle resolvers were moved to
251 // `includes/core/payload.php`. Function names didn't
252 // change; existing callers find them via PHP's runtime function
253 // resolution. desktop-mode.php loads payload.php right after
254 // helpers.php so the foundational helpers (desktop_mode_is_enabled
255 // etc.) are present when payload functions are invoked.
256