PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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
← All changes | includes/desktop-files/rest.php +483 -345 0.9.31.1.10 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Files REST routes.
3 + * OpenStation — Files REST routes.
4 4 *
5 5 * Routes under `/desktop-mode/v1/files`:
6 6 *
7 7 * GET /placements?folder=<id> List the viewer's placements
@@ -40,27 +40,27 @@
40 40 * Permission: every route requires a logged-in user with desktop
41 41 * mode enabled. Per-row gating happens inside the store. On top of
42 42 * that base, the share/accept/deny/leave routes also gate on the
43 43 * viewer's folder-sharing OS Setting via
44 - * `desktop_mode_files_rest_share_permission`, `/users/search`
44 + * `openstation_files_rest_share_permission`, `/users/search`
45 45 * additionally requires `edit_posts`, and the sharing-tables purge
46 46 * requires `manage_options`.
47 47 *
48 - * @package WPDesktopMode
49 - * @since 0.9.0
48 + * @package OpenStation
50 49 */
51 50
52 51 defined( 'ABSPATH' ) || exit;
53 52
54 53 /**
55 - * @since 0.9.0
54 + * Base permission check shared by the desktop-files REST routes:
55 + * requires a logged-in user with OpenStation enabled.
56 56 */
57 -function desktop_mode_files_rest_permission() {
57 +function openstation_files_rest_permission() {
58 58 if ( ! is_user_logged_in() ) {
59 - return new WP_Error( 'desktop_mode_files_unauthenticated', __( 'You must be logged in.', 'desktop-mode' ), array( 'status' => 401 ) );
59 + return new WP_Error( 'openstation_files_unauthenticated', __( 'You must be logged in.', 'desktop-mode' ), array( 'status' => 401 ) );
60 60 }
61 - if ( function_exists( 'desktop_mode_is_enabled' ) && ! desktop_mode_is_enabled( get_current_user_id() ) ) {
62 - return new WP_Error( 'desktop_mode_files_disabled', __( 'Desktop mode is not enabled for this user.', 'desktop-mode' ), array( 'status' => 403 ) );
61 + if ( function_exists( 'openstation_is_enabled' ) && ! openstation_is_enabled( get_current_user_id() ) ) {
62 + return new WP_Error( 'openstation_files_disabled', __( 'OpenStation is not enabled for this user.', 'desktop-mode' ), array( 'status' => 403 ) );
63 63 }
64 64 return true;
65 65 }
66 66
@@ -65,24 +65,22 @@
65 65 }
66 66
67 67 /**
68 68 * Permission callback layered ON TOP of
69 - * `desktop_mode_files_rest_permission` for every share-related
69 + * `openstation_files_rest_permission` for every share-related
70 70 * route. Returns a 404 (looks the same as a route that doesn't
71 71 * exist) when the viewer has the folder-sharing feature toggled
72 72 * off in OS Settings — no information leak about whether the
73 73 * feature is even installed.
74 - *
75 - * @since 0.8.5
76 74 */
77 -function desktop_mode_files_rest_share_permission() {
78 - $base = desktop_mode_files_rest_permission();
75 +function openstation_files_rest_share_permission() {
76 + $base = openstation_files_rest_permission();
79 77 if ( is_wp_error( $base ) ) {
80 78 return $base;
81 79 }
82 80 if (
83 - function_exists( 'desktop_mode_files_sharing_enabled_for' )
84 - && ! desktop_mode_files_sharing_enabled_for( get_current_user_id() )
81 + function_exists( 'openstation_files_sharing_enabled_for' )
82 + && ! openstation_files_sharing_enabled_for( get_current_user_id() )
85 83 ) {
86 84 return new WP_Error(
87 85 'rest_no_route',
88 86 __( 'No route was found matching the URL and request method.', 'desktop-mode' ),
@@ -96,15 +94,13 @@
96 94 * Permission callback for the destructive site-admin actions
97 95 * (currently: drop the folder-sharing tables). Requires
98 96 * `manage_options` — site-wide schema mutation should never be
99 97 * exposed below that capability.
100 - *
101 - * @since 0.8.5
102 98 */
103 -function desktop_mode_files_rest_admin_permission() {
99 +function openstation_files_rest_admin_permission() {
104 100 if ( ! current_user_can( 'manage_options' ) ) {
105 101 return new WP_Error(
106 - 'desktop_mode_files_forbidden',
102 + 'openstation_files_forbidden',
107 103 __( 'You do not have permission to perform this action.', 'desktop-mode' ),
108 104 array( 'status' => 403 )
109 105 );
110 106 }
@@ -112,211 +108,374 @@
112 108 }
113 109
114 110 /**
115 111 * Register the routes.
116 - *
117 - * @since 0.9.0
118 112 */
119 -function desktop_mode_files_register_rest_routes() {
113 +function openstation_files_register_rest_routes() {
120 114 $ns = 'desktop-mode/v1';
121 115
122 - register_rest_route( $ns, '/files/placements', array(
116 + register_rest_route(
117 + $ns,
118 + '/files/placements',
123 119 array(
124 - 'methods' => WP_REST_Server::READABLE,
125 - 'permission_callback' => 'desktop_mode_files_rest_permission',
126 - 'callback' => 'desktop_mode_files_rest_list_placements',
127 - 'args' => array(
128 - 'folder' => array( 'type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint' ),
120 + array(
121 + 'methods' => WP_REST_Server::READABLE,
122 + 'permission_callback' => 'openstation_files_rest_permission',
123 + 'callback' => 'openstation_files_rest_list_placements',
124 + 'args' => array(
125 + 'folder' => array(
126 + 'type' => 'integer',
127 + 'default' => 0,
128 + 'sanitize_callback' => 'absint',
129 + ),
130 + ),
129 131 ),
130 - ),
132 + array(
133 + 'methods' => WP_REST_Server::CREATABLE,
134 + 'permission_callback' => 'openstation_files_rest_permission',
135 + 'callback' => 'openstation_files_rest_create_placement',
136 + 'args' => array(
137 + 'parentId' => array(
138 + 'type' => 'integer',
139 + 'default' => 0,
140 + ),
141 + 'type' => array(
142 + 'type' => 'string',
143 + 'required' => true,
144 + ),
145 + 'ref' => array(
146 + 'type' => 'string',
147 + 'required' => true,
148 + ),
149 + 'x' => array(
150 + 'type' => 'integer',
151 + 'default' => 0,
152 + ),
153 + 'y' => array(
154 + 'type' => 'integer',
155 + 'default' => 0,
156 + ),
157 + 'sortOrder' => array(
158 + 'type' => 'integer',
159 + 'default' => 0,
160 + ),
161 + 'meta' => array(
162 + 'type' => 'object',
163 + 'required' => false,
164 + ),
165 + ),
166 + ),
167 + )
168 + );
169 +
170 + register_rest_route(
171 + $ns,
172 + '/files/placements/(?P<id>\d+)',
131 173 array(
132 - 'methods' => WP_REST_Server::CREATABLE,
133 - 'permission_callback' => 'desktop_mode_files_rest_permission',
134 - 'callback' => 'desktop_mode_files_rest_create_placement',
135 - 'args' => array(
136 - 'parentId' => array( 'type' => 'integer', 'default' => 0 ),
137 - 'type' => array( 'type' => 'string', 'required' => true ),
138 - 'ref' => array( 'type' => 'string', 'required' => true ),
139 - 'x' => array( 'type' => 'integer', 'default' => 0 ),
140 - 'y' => array( 'type' => 'integer', 'default' => 0 ),
141 - 'sortOrder' => array( 'type' => 'integer', 'default' => 0 ),
142 - 'meta' => array( 'type' => 'object', 'required' => false ),
174 + array(
175 + 'methods' => WP_REST_Server::EDITABLE,
176 + 'permission_callback' => 'openstation_files_rest_permission',
177 + 'callback' => 'openstation_files_rest_update_placement',
143 178 ),
144 - ),
145 - ) );
179 + array(
180 + 'methods' => WP_REST_Server::DELETABLE,
181 + 'permission_callback' => 'openstation_files_rest_permission',
182 + 'callback' => 'openstation_files_rest_delete_placement',
183 + ),
184 + )
185 + );
146 186
147 - register_rest_route( $ns, '/files/placements/(?P<id>\d+)', array(
187 + register_rest_route(
188 + $ns,
189 + '/files/folders',
148 190 array(
149 - 'methods' => WP_REST_Server::EDITABLE,
150 - 'permission_callback' => 'desktop_mode_files_rest_permission',
151 - 'callback' => 'desktop_mode_files_rest_update_placement',
152 - ),
191 + array(
192 + 'methods' => WP_REST_Server::READABLE,
193 + 'permission_callback' => 'openstation_files_rest_permission',
194 + 'callback' => 'openstation_files_rest_list_folders',
195 + ),
196 + array(
197 + 'methods' => WP_REST_Server::CREATABLE,
198 + 'permission_callback' => 'openstation_files_rest_permission',
199 + 'callback' => 'openstation_files_rest_create_folder',
200 + 'args' => array(
201 + 'name' => array(
202 + 'type' => 'string',
203 + 'required' => true,
204 + ),
205 + 'shareMode' => array(
206 + 'type' => 'string',
207 + 'default' => 'private',
208 + ),
209 + 'shareMeta' => array(
210 + 'type' => 'object',
211 + 'required' => false,
212 + ),
213 + ),
214 + ),
215 + )
216 + );
217 +
218 + register_rest_route(
219 + $ns,
220 + '/files/folders/(?P<id>\d+)',
153 221 array(
154 - 'methods' => WP_REST_Server::DELETABLE,
155 - 'permission_callback' => 'desktop_mode_files_rest_permission',
156 - 'callback' => 'desktop_mode_files_rest_delete_placement',
157 - ),
158 - ) );
222 + array(
223 + 'methods' => WP_REST_Server::EDITABLE,
224 + 'permission_callback' => 'openstation_files_rest_permission',
225 + 'callback' => 'openstation_files_rest_update_folder',
226 + ),
227 + array(
228 + 'methods' => WP_REST_Server::DELETABLE,
229 + 'permission_callback' => 'openstation_files_rest_permission',
230 + 'callback' => 'openstation_files_rest_delete_folder',
231 + ),
232 + )
233 + );
159 234
160 - register_rest_route( $ns, '/files/folders', array(
235 + register_rest_route(
236 + $ns,
237 + '/files/associations',
161 238 array(
162 - 'methods' => WP_REST_Server::READABLE,
163 - 'permission_callback' => 'desktop_mode_files_rest_permission',
164 - 'callback' => 'desktop_mode_files_rest_list_folders',
165 - ),
166 - array(
167 - 'methods' => WP_REST_Server::CREATABLE,
168 - 'permission_callback' => 'desktop_mode_files_rest_permission',
169 - 'callback' => 'desktop_mode_files_rest_create_folder',
239 + 'methods' => 'PUT',
240 + 'permission_callback' => 'openstation_files_rest_permission',
241 + 'callback' => 'openstation_files_rest_save_associations',
170 242 'args' => array(
171 - 'name' => array( 'type' => 'string', 'required' => true ),
172 - 'shareMode' => array( 'type' => 'string', 'default' => 'private' ),
173 - 'shareMeta' => array( 'type' => 'object', 'required' => false ),
243 + 'associations' => array(
244 + 'type' => 'object',
245 + 'required' => true,
246 + ),
174 247 ),
175 - ),
176 - ) );
248 + )
249 + );
177 250
178 - register_rest_route( $ns, '/files/folders/(?P<id>\d+)', array(
179 - array(
180 - 'methods' => WP_REST_Server::EDITABLE,
181 - 'permission_callback' => 'desktop_mode_files_rest_permission',
182 - 'callback' => 'desktop_mode_files_rest_update_folder',
183 - ),
184 - array(
185 - 'methods' => WP_REST_Server::DELETABLE,
186 - 'permission_callback' => 'desktop_mode_files_rest_permission',
187 - 'callback' => 'desktop_mode_files_rest_delete_folder',
188 - ),
189 - ) );
190 -
191 - register_rest_route( $ns, '/files/associations', array(
192 - 'methods' => 'PUT',
193 - 'permission_callback' => 'desktop_mode_files_rest_permission',
194 - 'callback' => 'desktop_mode_files_rest_save_associations',
195 - 'args' => array(
196 - 'associations' => array( 'type' => 'object', 'required' => true ),
197 - ),
198 - ) );
199 -
200 251 // Every share-related route gates on the user's
201 252 // `foldersSharingEnabled` OS Setting via
202 - // `desktop_mode_files_rest_share_permission` — when a user has
253 + // `openstation_files_rest_share_permission` — when a user has
203 254 // flipped sharing off, these routes return 404 (looks the same
204 255 // as a feature that isn't installed; no info leak about the
205 256 // kill switch's existence).
206 - register_rest_route( $ns, '/files/folders/(?P<id>\d+)/shares', array(
257 + register_rest_route(
258 + $ns,
259 + '/files/folders/(?P<id>\d+)/shares',
207 260 array(
208 - 'methods' => WP_REST_Server::READABLE,
209 - 'permission_callback' => 'desktop_mode_files_rest_share_permission',
210 - 'callback' => 'desktop_mode_files_rest_list_shares',
211 - ),
212 - array(
213 - 'methods' => WP_REST_Server::CREATABLE,
214 - 'permission_callback' => 'desktop_mode_files_rest_share_permission',
215 - 'callback' => 'desktop_mode_files_rest_create_share',
216 - 'args' => array(
217 - 'principalType' => array(
218 - 'type' => 'string',
219 - 'enum' => array( 'user', 'role' ),
220 - 'required' => true,
261 + array(
262 + 'methods' => WP_REST_Server::READABLE,
263 + 'permission_callback' => 'openstation_files_rest_share_permission',
264 + 'callback' => 'openstation_files_rest_list_shares',
265 + ),
266 + array(
267 + 'methods' => WP_REST_Server::CREATABLE,
268 + 'permission_callback' => 'openstation_files_rest_share_permission',
269 + 'callback' => 'openstation_files_rest_create_share',
270 + 'args' => array(
271 + 'principalType' => array(
272 + 'type' => 'string',
273 + 'enum' => array( 'user', 'role' ),
274 + 'required' => true,
275 + ),
276 + 'principalRef' => array(
277 + 'type' => 'string',
278 + 'required' => true,
279 + ),
280 + 'capability' => array(
281 + 'type' => 'string',
282 + 'enum' => array( 'read', 'write' ),
283 + 'default' => 'read',
284 + ),
221 285 ),
222 - 'principalRef' => array( 'type' => 'string', 'required' => true ),
223 - 'capability' => array(
224 - 'type' => 'string',
225 - 'enum' => array( 'read', 'write' ),
226 - 'default' => 'read',
227 - ),
228 286 ),
229 - ),
230 - ) );
287 + )
288 + );
231 289
232 - register_rest_route( $ns, '/files/folders/(?P<id>\d+)/shares/(?P<shareId>\d+)', array(
290 + register_rest_route(
291 + $ns,
292 + '/files/folders/(?P<id>\d+)/shares/(?P<shareId>\d+)',
233 293 array(
234 - 'methods' => WP_REST_Server::EDITABLE,
235 - 'permission_callback' => 'desktop_mode_files_rest_share_permission',
236 - 'callback' => 'desktop_mode_files_rest_update_share',
237 - 'args' => array(
238 - 'capability' => array(
239 - 'type' => 'string',
240 - 'enum' => array( 'read', 'write' ),
241 - 'required' => true,
294 + array(
295 + 'methods' => WP_REST_Server::EDITABLE,
296 + 'permission_callback' => 'openstation_files_rest_share_permission',
297 + 'callback' => 'openstation_files_rest_update_share',
298 + 'args' => array(
299 + 'capability' => array(
300 + 'type' => 'string',
301 + 'enum' => array( 'read', 'write' ),
302 + 'required' => true,
303 + ),
242 304 ),
243 305 ),
244 - ),
306 + array(
307 + 'methods' => WP_REST_Server::DELETABLE,
308 + 'permission_callback' => 'openstation_files_rest_share_permission',
309 + 'callback' => 'openstation_files_rest_delete_share',
310 + ),
311 + )
312 + );
313 +
314 + register_rest_route(
315 + $ns,
316 + '/files/folders/(?P<id>\d+)/shares/(?P<shareId>\d+)/accept',
245 317 array(
246 - 'methods' => WP_REST_Server::DELETABLE,
247 - 'permission_callback' => 'desktop_mode_files_rest_share_permission',
248 - 'callback' => 'desktop_mode_files_rest_delete_share',
249 - ),
250 - ) );
318 + 'methods' => WP_REST_Server::CREATABLE,
319 + 'permission_callback' => 'openstation_files_rest_share_permission',
320 + 'callback' => 'openstation_files_rest_accept_share',
321 + )
322 + );
251 323
252 - register_rest_route( $ns, '/files/folders/(?P<id>\d+)/shares/(?P<shareId>\d+)/accept', array(
253 - 'methods' => WP_REST_Server::CREATABLE,
254 - 'permission_callback' => 'desktop_mode_files_rest_share_permission',
255 - 'callback' => 'desktop_mode_files_rest_accept_share',
256 - ) );
324 + register_rest_route(
325 + $ns,
326 + '/files/folders/(?P<id>\d+)/shares/(?P<shareId>\d+)/deny',
327 + array(
328 + 'methods' => WP_REST_Server::CREATABLE,
329 + 'permission_callback' => 'openstation_files_rest_share_permission',
330 + 'callback' => 'openstation_files_rest_deny_share',
331 + )
332 + );
257 333
258 - register_rest_route( $ns, '/files/folders/(?P<id>\d+)/shares/(?P<shareId>\d+)/deny', array(
259 - 'methods' => WP_REST_Server::CREATABLE,
260 - 'permission_callback' => 'desktop_mode_files_rest_share_permission',
261 - 'callback' => 'desktop_mode_files_rest_deny_share',
262 - ) );
334 + register_rest_route(
335 + $ns,
336 + '/files/folders/(?P<id>\d+)/leave',
337 + array(
338 + 'methods' => WP_REST_Server::CREATABLE,
339 + 'permission_callback' => 'openstation_files_rest_share_permission',
340 + 'callback' => 'openstation_files_rest_leave_folder',
341 + )
342 + );
263 343
264 - register_rest_route( $ns, '/files/folders/(?P<id>\d+)/leave', array(
265 - 'methods' => WP_REST_Server::CREATABLE,
266 - 'permission_callback' => 'desktop_mode_files_rest_share_permission',
267 - 'callback' => 'desktop_mode_files_rest_leave_folder',
268 - ) );
344 + register_rest_route(
345 + $ns,
346 + '/files/users/search',
347 + array(
348 + 'methods' => WP_REST_Server::READABLE,
349 + 'permission_callback' => 'openstation_files_rest_search_users_permission',
350 + 'callback' => 'openstation_files_rest_search_users',
351 + 'args' => array(
352 + 'q' => array(
353 + 'type' => 'string',
354 + 'default' => '',
355 + ),
356 + 'exclude' => array(
357 + 'type' => 'string',
358 + 'default' => '',
359 + ),
360 + ),
361 + )
362 + );
269 363
270 - register_rest_route( $ns, '/files/users/search', array(
271 - 'methods' => WP_REST_Server::READABLE,
272 - 'permission_callback' => 'desktop_mode_files_rest_search_users_permission',
273 - 'callback' => 'desktop_mode_files_rest_search_users',
274 - 'args' => array(
275 - 'q' => array( 'type' => 'string', 'default' => '' ),
276 - 'exclude' => array( 'type' => 'string', 'default' => '' ),
277 - ),
278 - ) );
279 -
280 364 // Site-admin only: destructive cleanup that drops the folder-
281 365 // sharing tables outright (legacy + current). Surfaced from
282 366 // the OS Settings → Features → Advanced panel.
283 - register_rest_route( $ns, '/files/folder-sharing-tables/purge', array(
284 - 'methods' => WP_REST_Server::CREATABLE,
285 - 'permission_callback' => 'desktop_mode_files_rest_admin_permission',
286 - 'callback' => 'desktop_mode_files_rest_purge_sharing_tables',
287 - ) );
367 + register_rest_route(
368 + $ns,
369 + '/files/folder-sharing-tables/purge',
370 + array(
371 + 'methods' => WP_REST_Server::CREATABLE,
372 + 'permission_callback' => 'openstation_files_rest_admin_permission',
373 + 'callback' => 'openstation_files_rest_purge_sharing_tables',
374 + )
375 + );
288 376 }
289 -add_action( 'rest_api_init', 'desktop_mode_files_register_rest_routes' );
377 +add_action( 'rest_api_init', 'openstation_files_register_rest_routes' );
290 378
291 379 /**
380 + * Inline the root folder's placements into the boot-time shell
381 + * config so the desktop file grid hydrates without a REST
382 + * round-trip — this was the only REST call the shell had to await
383 + * before revealing the desktop. Mirrors the GET /placements handler
384 + * for `folder=0` exactly (same orphan backfill, same shape) so the
385 + * client store can't tell the difference; the JS consumer
386 + * (`src/desktop-files/layer.ts`) consumes the key one-shot, so any
387 + * later re-hydration still goes through REST for fresh state.
388 + *
389 + * The `openstation_shell_config` filter only runs while rendering
390 + * the shell for an enabled, logged-in user — the same gate the REST
391 + * permission callback enforces.
392 + *
393 + * @param array $config Shell config.
394 + * @return array
395 + */
396 +function openstation_files_inject_boot_placements( $config ) {
397 + $user_id = get_current_user_id();
398 + if ( $user_id <= 0 ) {
399 + return $config;
400 + }
401 + openstation_files_auto_place_orphans( $user_id );
402 + $rows = openstation_files_get_for_user_folder( $user_id, 0 );
403 + $out = array();
404 + foreach ( $rows as $row ) {
405 + $out[] = openstation_files_shape_placement( $row );
406 + }
407 + $config['filesBootPlacements'] = $out;
408 + return $config;
409 +}
410 +add_filter( 'openstation_shell_config', 'openstation_files_inject_boot_placements', 20 );
411 +
412 +/**
413 + * Inline the viewer's visible folder rows into the boot-time shell
414 + * config, the same way the root placements are.
415 + *
416 + * The client keeps a folders map alongside its placements, and
417 + * anything that needs to know a folder's OWNER reads it there —
418 + * notably the "Share folder" title-bar button, which is owner-only
419 + * and has nothing but a window id to work from. Nothing on the
420 + * normal boot path filled that map: placement hydration populates
421 + * placements, and `listFolders()` only ran after a create, a rename
422 + * or an untrash. So a plain reload left every folder ownerless, and
423 + * the owner of a folder lost the one control that manages its
424 + * sharing until something happened to repopulate the map.
425 + *
426 + * Mirrors GET /folders exactly (same visibility resolution — owned
427 + * folders plus accepted shares plus `share_mode='all'` — same
428 + * shape), and the client consumes it one-shot, so any later
429 + * re-hydration still goes through REST for fresh state.
430 + *
431 + * @param array $config Shell config.
432 + * @return array
433 + */
434 +function openstation_files_inject_boot_folders( $config ) {
435 + $user_id = get_current_user_id();
436 + if ( $user_id <= 0 ) {
437 + return $config;
438 + }
439 + $out = array();
440 + foreach ( openstation_files_get_visible_folders( $user_id ) as $row ) {
441 + $out[] = openstation_files_shape_folder( $row );
442 + }
443 + $config['filesBootFolders'] = $out;
444 + return $config;
445 +}
446 +add_filter( 'openstation_shell_config', 'openstation_files_inject_boot_folders', 20 );
447 +
448 +/**
292 449 * GET /placements
293 450 */
294 -function desktop_mode_files_rest_list_placements( WP_REST_Request $req ) {
451 +function openstation_files_rest_list_placements( WP_REST_Request $req ) {
295 452 $user_id = get_current_user_id();
296 453 $parent_id = (int) $req->get_param( 'folder' );
297 454 // Self-healing backfill — see
298 - // `desktop_mode_files_auto_place_orphan_folders` for the why.
455 + // `openstation_files_auto_place_orphan_folders` for the why.
299 456 // Only runs at the root because that's the only context where
300 457 // auto-placing an orphan folder as a tile is unambiguous.
301 458 if ( 0 === $parent_id ) {
302 - desktop_mode_files_auto_place_orphans( $user_id );
459 + openstation_files_auto_place_orphans( $user_id );
303 460 }
304 - $rows = desktop_mode_files_get_for_user_folder( $user_id, $parent_id );
305 - $out = array();
461 + $rows = openstation_files_get_for_user_folder( $user_id, $parent_id );
462 + $out = array();
306 463 foreach ( $rows as $row ) {
307 - $out[] = desktop_mode_files_shape_placement( $row );
464 + $out[] = openstation_files_shape_placement( $row );
308 465 }
309 - return rest_ensure_response( array(
310 - 'placements' => $out,
311 - 'folderId' => $parent_id,
312 - ) );
466 + return rest_ensure_response(
467 + array(
468 + 'placements' => $out,
469 + 'folderId' => $parent_id,
470 + )
471 + );
313 472 }
314 473
315 474 /**
316 475 * POST /placements
317 476 */
318 -function desktop_mode_files_rest_create_placement( WP_REST_Request $req ) {
477 +function openstation_files_rest_create_placement( WP_REST_Request $req ) {
319 478 $type = (string) $req->get_param( 'type' );
320 479 $ref = (string) $req->get_param( 'ref' );
321 480 $meta = $req->get_param( 'meta' );
322 481
@@ -324,17 +483,17 @@
324 483 // `meta.iconUrl` so the tile renderer can paint it without the
325 484 // browser making a third-party request on every render. Other
326 485 // types skip the resolver entirely (no extra fetch latency).
327 486 if ( 'link' === $type && '' !== $ref ) {
328 - $icon_data_uri = desktop_mode_resolve_favicon( $ref );
487 + $icon_data_uri = openstation_resolve_favicon( $ref );
329 488 if ( is_string( $icon_data_uri ) && '' !== $icon_data_uri ) {
330 - $meta_arr = is_array( $meta ) ? $meta : array();
331 - $meta_arr['iconUrl'] = $icon_data_uri;
332 - $meta = $meta_arr;
489 + $meta_arr = is_array( $meta ) ? $meta : array();
490 + $meta_arr['iconUrl'] = $icon_data_uri;
491 + $meta = $meta_arr;
333 492 }
334 493 }
335 494
336 - $id = desktop_mode_files_place(
495 + $id = openstation_files_place(
337 496 get_current_user_id(),
338 497 (int) $req->get_param( 'parentId' ),
339 498 $type,
340 499 $ref,
@@ -347,43 +506,50 @@
347 506 );
348 507 if ( is_wp_error( $id ) ) {
349 508 return $id;
350 509 }
351 - $row = desktop_mode_files_get_placement( $id );
352 - return rest_ensure_response( desktop_mode_files_shape_placement( $row ) );
510 + $row = openstation_files_get_placement( $id );
511 + return rest_ensure_response( openstation_files_shape_placement( $row ) );
353 512 }
354 513
355 514 /**
356 515 * PATCH /placements/<id>
357 516 */
358 -function desktop_mode_files_rest_update_placement( WP_REST_Request $req ) {
517 +function openstation_files_rest_update_placement( WP_REST_Request $req ) {
359 518 $id = (int) $req['id'];
360 - $body = $req->get_json_params() ?: $req->get_params();
361 - $current = desktop_mode_files_get_placement( $id );
519 + $json = $req->get_json_params();
520 + $body = $json ? $json : $req->get_params();
521 + $current = openstation_files_get_placement( $id );
362 522 if ( ! $current ) {
363 - return new WP_Error( 'desktop_mode_files_not_found', __( 'Placement not found.', 'desktop-mode' ), array( 'status' => 404 ) );
523 + return new WP_Error( 'openstation_files_not_found', __( 'Placement not found.', 'desktop-mode' ), array( 'status' => 404 ) );
364 524 }
365 - $conflict = desktop_mode_files_check_if_match( (int) $current['updated_at_ms'], $req, $current );
525 + $conflict = openstation_files_check_if_match( (int) $current['updated_at_ms'], $req, $current );
366 526 if ( is_wp_error( $conflict ) ) {
367 527 return $conflict;
368 528 }
369 529 $changes = array();
370 - foreach ( array( 'parentId' => 'parent_id', 'x' => 'x', 'y' => 'y', 'sortOrder' => 'sort_order', 'meta' => 'meta' ) as $in => $col ) {
530 + foreach ( array(
531 + 'parentId' => 'parent_id',
532 + 'x' => 'x',
533 + 'y' => 'y',
534 + 'sortOrder' => 'sort_order',
535 + 'meta' => 'meta',
536 + ) as $in => $col ) {
371 537 if ( array_key_exists( $in, $body ) ) {
372 538 $changes[ $col ] = $body[ $in ];
373 539 }
374 540 }
375 - $ok = desktop_mode_files_move( $id, get_current_user_id(), $changes );
541 + $ok = openstation_files_move( $id, get_current_user_id(), $changes );
376 542 if ( is_wp_error( $ok ) ) {
377 543 return $ok;
378 544 }
379 - return rest_ensure_response( desktop_mode_files_shape_placement( desktop_mode_files_get_placement( $id ) ) );
545 + return rest_ensure_response( openstation_files_shape_placement( openstation_files_get_placement( $id ) ) );
380 546 }
381 547
382 548 /**
383 549 * DELETE /placements/<id>
384 550 */
385 -function desktop_mode_files_rest_delete_placement( WP_REST_Request $req ) {
551 +function openstation_files_rest_delete_placement( WP_REST_Request $req ) {
386 552 $id = (int) $req['id'];
387 553 $user_id = get_current_user_id();
388 554 // `force=1` query param permanently deletes (purges the row).
389 555 // Default DELETE soft-trashes — the row lands in the recycle
@@ -391,10 +557,10 @@
391 557 // uses on every other resource.
392 558 $force = '1' === (string) $req->get_param( 'force' )
393 559 || true === $req->get_param( 'force' );
394 560 $ok = $force
395 - ? desktop_mode_files_purge_placement( $user_id, $id )
396 - : desktop_mode_files_trash_placement( $user_id, $id );
561 + ? openstation_files_purge_placement( $user_id, $id )
562 + : openstation_files_trash_placement( $user_id, $id );
397 563 if ( is_wp_error( $ok ) ) {
398 564 return $ok;
399 565 }
400 566 return rest_ensure_response(
@@ -407,13 +573,13 @@
407 573
408 574 /**
409 575 * GET /folders
410 576 */
411 -function desktop_mode_files_rest_list_folders() {
412 - $rows = desktop_mode_files_get_visible_folders( get_current_user_id() );
577 +function openstation_files_rest_list_folders() {
578 + $rows = openstation_files_get_visible_folders( get_current_user_id() );
413 579 $out = array();
414 580 foreach ( $rows as $row ) {
415 - $out[] = desktop_mode_files_shape_folder( $row );
581 + $out[] = openstation_files_shape_folder( $row );
416 582 }
417 583 return rest_ensure_response( array( 'folders' => $out ) );
418 584 }
419 585
@@ -419,10 +585,10 @@
419 585
420 586 /**
421 587 * POST /folders
422 588 */
423 -function desktop_mode_files_rest_create_folder( WP_REST_Request $req ) {
424 - $id = desktop_mode_files_create_folder(
589 +function openstation_files_rest_create_folder( WP_REST_Request $req ) {
590 + $id = openstation_files_create_folder(
425 591 get_current_user_id(),
426 592 array(
427 593 'name' => (string) $req->get_param( 'name' ),
428 594 'share_mode' => (string) $req->get_param( 'shareMode' ),
@@ -431,53 +597,58 @@
431 597 );
432 598 if ( is_wp_error( $id ) ) {
433 599 return $id;
434 600 }
435 - return rest_ensure_response( desktop_mode_files_shape_folder( desktop_mode_files_get_folder( $id ) ) );
601 + return rest_ensure_response( openstation_files_shape_folder( openstation_files_get_folder( $id ) ) );
436 602 }
437 603
438 604 /**
439 605 * PATCH /folders/<id>
440 606 */
441 -function desktop_mode_files_rest_update_folder( WP_REST_Request $req ) {
607 +function openstation_files_rest_update_folder( WP_REST_Request $req ) {
442 608 $id = (int) $req['id'];
443 - $body = $req->get_json_params() ?: $req->get_params();
444 - $current = desktop_mode_files_get_folder( $id );
609 + $json = $req->get_json_params();
610 + $body = $json ? $json : $req->get_params();
611 + $current = openstation_files_get_folder( $id );
445 612 if ( ! $current ) {
446 - return new WP_Error( 'desktop_mode_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
613 + return new WP_Error( 'openstation_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
447 614 }
448 - $conflict = desktop_mode_files_check_if_match( (int) $current['updated_at_ms'], $req, $current );
615 + $conflict = openstation_files_check_if_match( (int) $current['updated_at_ms'], $req, $current );
449 616 if ( is_wp_error( $conflict ) ) {
450 617 return $conflict;
451 618 }
452 619 $changes = array();
453 - foreach ( array( 'name' => 'name', 'shareMode' => 'share_mode', 'shareMeta' => 'share_meta' ) as $in => $col ) {
620 + foreach ( array(
621 + 'name' => 'name',
622 + 'shareMode' => 'share_mode',
623 + 'shareMeta' => 'share_meta',
624 + ) as $in => $col ) {
454 625 if ( array_key_exists( $in, $body ) ) {
455 626 $changes[ $col ] = $body[ $in ];
456 627 }
457 628 }
458 - $ok = desktop_mode_files_update_folder( $id, get_current_user_id(), $changes );
629 + $ok = openstation_files_update_folder( $id, get_current_user_id(), $changes );
459 630 if ( is_wp_error( $ok ) ) {
460 631 return $ok;
461 632 }
462 - return rest_ensure_response( desktop_mode_files_shape_folder( desktop_mode_files_get_folder( $id ) ) );
633 + return rest_ensure_response( openstation_files_shape_folder( openstation_files_get_folder( $id ) ) );
463 634 }
464 635
465 636 /**
466 637 * DELETE /folders/<id>
467 638 */
468 -function desktop_mode_files_rest_delete_folder( WP_REST_Request $req ) {
639 +function openstation_files_rest_delete_folder( WP_REST_Request $req ) {
469 640 $id = (int) $req['id'];
470 641 $user_id = get_current_user_id();
471 642 $force = '1' === (string) $req->get_param( 'force' )
472 643 || true === $req->get_param( 'force' );
473 644 // Default DELETE soft-trashes the folder + cascades to child
474 - // placements (see `desktop_mode_files_trash_folder`). `force=1`
645 + // placements (see `openstation_files_trash_folder`). `force=1`
475 646 // permanently deletes both the folder row AND every child
476 647 // placement that was trashed via the cascade.
477 648 $ok = $force
478 - ? desktop_mode_files_purge_folder( $user_id, $id )
479 - : desktop_mode_files_trash_folder( $user_id, $id );
649 + ? openstation_files_purge_folder( $user_id, $id )
650 + : openstation_files_trash_folder( $user_id, $id );
480 651 if ( is_wp_error( $ok ) ) {
481 652 return $ok;
482 653 }
483 654 return rest_ensure_response(
@@ -490,9 +661,9 @@
490 661
491 662 /**
492 663 * PUT /associations — replaces the entire user-association map.
493 664 */
494 -function desktop_mode_files_rest_save_associations( WP_REST_Request $req ) {
665 +function openstation_files_rest_save_associations( WP_REST_Request $req ) {
495 666 $assoc = (array) $req->get_param( 'associations' );
496 667 $clean = array();
497 668 foreach ( $assoc as $type => $opener_id ) {
498 669 $type = sanitize_key( (string) $type );
@@ -501,25 +672,25 @@
501 672 continue;
502 673 }
503 674 $clean[ $type ] = $opener_id;
504 675 }
505 - update_user_meta( get_current_user_id(), DESKTOP_MODE_FILE_ASSOCIATIONS_META, $clean );
506 - return rest_ensure_response( array(
507 - 'associations' => desktop_mode_get_user_file_associations( get_current_user_id() ),
508 - ) );
676 + update_user_meta( get_current_user_id(), OPENSTATION_FILE_ASSOCIATIONS_META, $clean );
677 + return rest_ensure_response(
678 + array(
679 + 'associations' => openstation_get_user_file_associations( get_current_user_id() ),
680 + )
681 + );
509 682 }
510 683
511 684 /**
512 685 * Shape a placement row for the wire — converts snake_case to
513 - * camelCase and merges in the resolved `Desktop_Mode_File`
686 + * camelCase and merges in the resolved `OpenStation_File`
514 687 * shape so the JS side can render without a second fetch.
515 688 *
516 - * @since 0.9.0
517 - *
518 689 * @param array|null $row Normalized placement row.
519 690 * @return array
520 691 */
521 -function desktop_mode_files_shape_placement( $row ) {
692 +function openstation_files_shape_placement( $row ) {
522 693 if ( ! is_array( $row ) ) {
523 694 return array();
524 695 }
525 696 // Access-gated rows (shared-folder view, viewer lacks read on
@@ -538,9 +709,9 @@
538 709 'previewUrl' => '',
539 710 'exists' => true,
540 711 );
541 712 } else {
542 - $file = desktop_mode_resolve_file( $row['file_type'], $row['file_ref'] );
713 + $file = openstation_resolve_file( $row['file_type'], $row['file_ref'] );
543 714 $shape = $file ? $file->serialize() : array(
544 715 'type' => $row['file_type'],
545 716 'ref' => $row['file_ref'],
546 717 'title' => '',
@@ -558,70 +729,53 @@
558 729 // user staring at a tile that wouldn't move. Falls back to
559 730 // `false` when the helper isn't loaded (defensive — early-boot
560 731 // REST calls before trash.php is required can't grant permission
561 732 // they don't know about).
562 - $viewer_id = (int) get_current_user_id();
563 - $can_trash = false;
564 - if ( $viewer_id > 0 && function_exists( 'desktop_mode_files_user_can_trash_placement' ) ) {
565 - $can_trash = desktop_mode_files_user_can_trash_placement( $viewer_id, $row );
733 + $viewer_id = (int) get_current_user_id();
734 + $can_trash = false;
735 + if ( $viewer_id > 0 && function_exists( 'openstation_files_user_can_trash_placement' ) ) {
736 + $can_trash = openstation_files_user_can_trash_placement( $viewer_id, $row );
566 737 }
567 738
568 739 return array(
569 - 'id' => (int) $row['id'],
570 - 'parentId' => (int) $row['parent_id'],
571 - 'x' => (int) $row['x'],
572 - 'y' => (int) $row['y'],
573 - 'sortOrder' => (int) $row['sort_order'],
574 - 'updatedAtMs' => (int) $row['updated_at_ms'],
575 - 'meta' => isset( $row['meta'] ) ? $row['meta'] : null,
576 - 'file' => $shape,
740 + 'id' => (int) $row['id'],
741 + 'parentId' => (int) $row['parent_id'],
742 + 'x' => (int) $row['x'],
743 + 'y' => (int) $row['y'],
744 + 'sortOrder' => (int) $row['sort_order'],
745 + 'updatedAtMs' => (int) $row['updated_at_ms'],
746 + 'meta' => isset( $row['meta'] ) ? $row['meta'] : null,
747 + 'file' => $shape,
577 748 // `accessGated` is true when the viewer can't read the
578 749 // underlying entity but the placement is shown anyway (the
579 750 // shared-folder-view UX). Tile renderer surfaces it as a
580 751 // lock overlay + tooltip; the `file` shape above is redacted.
581 - 'accessGated' => $access_gated,
582 - 'canTrash' => $can_trash,
752 + 'accessGated' => $access_gated,
753 + 'canTrash' => $can_trash,
583 754 );
584 755 }
585 756
586 757 /**
587 - * @since 0.9.0
588 - *
589 758 * @param array|null $row Folder row.
590 759 * @return array
591 760 */
592 -function desktop_mode_files_shape_folder( $row ) {
761 +function openstation_files_shape_folder( $row ) {
593 762 if ( ! is_array( $row ) ) {
594 763 return array();
595 764 }
596 765 $shape = array(
597 - 'id' => (int) $row['id'],
598 - 'ownerId' => (int) $row['owner_id'],
599 - 'name' => (string) $row['name'],
600 - 'shareMode' => (string) $row['share_mode'],
601 - 'shareMeta' => isset( $row['share_meta'] ) ? $row['share_meta'] : null,
602 - 'updatedAtMs' => (int) $row['updated_at_ms'],
766 + 'id' => (int) $row['id'],
767 + 'ownerId' => (int) $row['owner_id'],
768 + 'name' => (string) $row['name'],
769 + 'shareMode' => (string) $row['share_mode'],
770 + 'shareMeta' => isset( $row['share_meta'] ) ? $row['share_meta'] : null,
771 + 'updatedAtMs' => (int) $row['updated_at_ms'],
603 772 );
604 - if ( function_exists( 'desktop_mode_files_get_folder_shares' ) ) {
605 - $shares = desktop_mode_files_get_folder_shares( (int) $row['id'] );
606 - $accepted_count = 0;
607 - $has_all = 'all' === (string) $row['share_mode'];
608 - foreach ( $shares as $s ) {
609 - if ( 'accepted' === $s['state'] ) {
610 - $accepted_count++;
611 - }
612 - }
613 - // `shared` is viewer-agnostic — recipients need it for the
614 - // shared-folder badge. The recipient COUNT is owner-internal
615 - // (the dedicated shares endpoint gates the full roster on
616 - // `share_can_manage`), so only managers get the real number;
617 - // every other viewer sees `0`, keeping the wire shape stable.
618 - $can_manage = function_exists( 'desktop_mode_files_share_can_manage' )
619 - && desktop_mode_files_share_can_manage( (int) $row['id'], get_current_user_id() );
620 - $shape['shareSummary'] = array(
621 - 'shared' => $has_all || $accepted_count > 0,
622 - 'recipientCount' => $can_manage ? $accepted_count + ( $has_all ? 1 : 0 ) : 0,
623 - );
773 + // Same summary `OpenStation_Folder_File::serialize()` puts on a
774 + // folder PLACEMENT, so a tile paints identically whichever
775 + // response it came from.
776 + if ( function_exists( 'openstation_files_folder_share_summary' ) ) {
777 + $shape['shareSummary'] = openstation_files_folder_share_summary( $row );
624 778 }
625 779 return $shape;
626 780 }
627 781
@@ -635,16 +789,14 @@
635 789 * The 409 body carries a structured `data` payload the client
636 790 * surfaces as a toast: `{ reason, actor: { id,name,avatar },
637 791 * current: { parentId, parentName, updatedAtMs } }`.
638 792 *
639 - * @since 0.8.5
640 - *
641 793 * @param int $current_ms Current `updated_at_ms` on the row.
642 794 * @param WP_REST_Request $req Inbound request.
643 795 * @param array $row Normalized row (placement or folder).
644 796 * @return WP_Error|null
645 797 */
646 -function desktop_mode_files_check_if_match( $current_ms, WP_REST_Request $req, $row ) {
798 +function openstation_files_check_if_match( $current_ms, WP_REST_Request $req, $row ) {
647 799 $header = $req->get_header( 'if_match' );
648 800 if ( null === $header || '' === $header ) {
649 801 return null;
650 802 }
@@ -659,9 +811,9 @@
659 811 // a non-shared-write workflow that still happens to be the
660 812 // right person; in shared-write the toast may be slightly
661 813 // misleading for the lifetime of pre-v10 rows. New mutations
662 814 // stamp the column accurately. See
663 - // `desktop_mode_files_ensure_updated_by_column`.
815 + // `openstation_files_ensure_updated_by_column`.
664 816 $actor_id = 0;
665 817 if ( isset( $row['updated_by'] ) && (int) $row['updated_by'] > 0 ) {
666 818 $actor_id = (int) $row['updated_by'];
667 819 } elseif ( isset( $row['owner_id'] ) ) {
@@ -671,9 +823,9 @@
671 823
672 824 $parent_id = isset( $row['parent_id'] ) ? (int) $row['parent_id'] : 0;
673 825 $parent_name = '';
674 826 if ( $parent_id > 0 ) {
675 - $parent_folder = desktop_mode_files_get_folder( $parent_id );
827 + $parent_folder = openstation_files_get_folder( $parent_id );
676 828 $parent_name = $parent_folder ? (string) $parent_folder['name'] : '';
677 829 }
678 830
679 831 $reason = 'parent_changed';
@@ -691,16 +843,16 @@
691 843 // drops the parent id/name, so a write attempt can't be used
692 844 // to enumerate other users' display names or folder names
693 845 // (this check runs BEFORE the store's ownership gate, so the
694 846 // 409 body must not leak what the later 403 would protect).
695 - $viewer_id = (int) get_current_user_id();
696 - $viewer_owns_row = isset( $row['owner_id'] ) && (int) $row['owner_id'] === $viewer_id;
697 - $viewer_can_see = $viewer_owns_row;
847 + $viewer_id = (int) get_current_user_id();
848 + $viewer_owns_row = isset( $row['owner_id'] ) && (int) $row['owner_id'] === $viewer_id;
849 + $viewer_can_see = $viewer_owns_row;
698 850 if ( ! $viewer_can_see && $parent_id > 0 && isset( $parent_folder ) && $parent_folder ) {
699 851 if ( (int) $parent_folder['owner_id'] === $viewer_id ) {
700 852 $viewer_can_see = true;
701 - } elseif ( function_exists( 'desktop_mode_folder_share_user_capability' ) ) {
702 - $viewer_can_see = 'none' !== desktop_mode_folder_share_user_capability( $parent_id, $viewer_id );
853 + } elseif ( function_exists( 'openstation_folder_share_user_capability' ) ) {
854 + $viewer_can_see = 'none' !== openstation_folder_share_user_capability( $parent_id, $viewer_id );
703 855 }
704 856 }
705 857 $actor_payload = array(
706 858 'id' => $viewer_can_see ? $actor_id : 0,
@@ -708,9 +860,9 @@
708 860 'avatar' => $viewer_can_see && $actor ? get_avatar_url( $actor->ID, array( 'size' => 32 ) ) : '',
709 861 );
710 862
711 863 return new WP_Error(
712 - 'desktop_mode_files_conflict',
864 + 'openstation_files_conflict',
713 865 __( 'This row was changed by another session.', 'desktop-mode' ),
714 866 array(
715 867 'status' => 409,
716 868 'data' => array(
@@ -728,14 +880,12 @@
728 880
729 881 /**
730 882 * Shape a share row for the wire.
731 883 *
732 - * @since 0.8.5
733 - *
734 884 * @param array|null $row Normalized share row.
735 885 * @return array
736 886 */
737 -function desktop_mode_files_shape_share( $row ) {
887 +function openstation_files_shape_share( $row ) {
738 888 if ( ! is_array( $row ) ) {
739 889 return array();
740 890 }
741 891 $shape = array(
@@ -749,15 +899,15 @@
749 899 'invitedAtMs' => (int) $row['invited_at_ms'],
750 900 'decidedAtMs' => isset( $row['decided_at_ms'] ) ? $row['decided_at_ms'] : null,
751 901 );
752 902 if ( 'user' === $row['principal_type'] ) {
753 - $uid = (int) $row['principal_ref'];
754 - $user = $uid > 0 ? get_userdata( $uid ) : null;
903 + $uid = (int) $row['principal_ref'];
904 + $user = $uid > 0 ? get_userdata( $uid ) : null;
755 905 $shape['displayName'] = $user ? $user->display_name : '';
756 906 $shape['avatarUrl'] = $user ? get_avatar_url( $uid, array( 'size' => 48 ) ) : '';
757 907 } else {
758 - $roles = wp_roles();
759 - $info = $roles && isset( $roles->roles[ $row['principal_ref'] ] ) ? $roles->roles[ $row['principal_ref'] ] : null;
908 + $roles = wp_roles();
909 + $info = $roles && isset( $roles->roles[ $row['principal_ref'] ] ) ? $roles->roles[ $row['principal_ref'] ] : null;
760 910 $shape['displayName'] = $info ? translate_user_role( (string) $info['name'] ) : (string) $row['principal_ref'];
761 911 $shape['avatarUrl'] = '';
762 912 }
763 913 return $shape;
@@ -765,22 +915,22 @@
765 915
766 916 /**
767 917 * GET /folders/<id>/shares — owner only.
768 918 */
769 -function desktop_mode_files_rest_list_shares( WP_REST_Request $req ) {
919 +function openstation_files_rest_list_shares( WP_REST_Request $req ) {
770 920 $folder_id = (int) $req['id'];
771 921 $user_id = get_current_user_id();
772 - if ( ! desktop_mode_files_share_can_manage( $folder_id, $user_id ) ) {
773 - return new WP_Error( 'desktop_mode_files_forbidden', __( 'You cannot view shares for this folder.', 'desktop-mode' ), array( 'status' => 403 ) );
922 + if ( ! openstation_files_share_can_manage( $folder_id, $user_id ) ) {
923 + return new WP_Error( 'openstation_files_forbidden', __( 'You cannot view shares for this folder.', 'desktop-mode' ), array( 'status' => 403 ) );
774 924 }
775 - $folder = desktop_mode_files_get_folder( $folder_id );
925 + $folder = openstation_files_get_folder( $folder_id );
776 926 if ( ! $folder ) {
777 - return new WP_Error( 'desktop_mode_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
927 + return new WP_Error( 'openstation_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
778 928 }
779 - $rows = desktop_mode_files_get_folder_shares( $folder_id );
780 - $out = array();
929 + $rows = openstation_files_get_folder_shares( $folder_id );
930 + $out = array();
781 931 foreach ( $rows as $row ) {
782 - $out[] = desktop_mode_files_shape_share( $row );
932 + $out[] = openstation_files_shape_share( $row );
783 933 }
784 934 return rest_ensure_response(
785 935 array(
786 936 'shares' => $out,
@@ -792,12 +942,12 @@
792 942
793 943 /**
794 944 * POST /folders/<id>/shares — owner only.
795 945 */
796 -function desktop_mode_files_rest_create_share( WP_REST_Request $req ) {
946 +function openstation_files_rest_create_share( WP_REST_Request $req ) {
797 947 $folder_id = (int) $req['id'];
798 948 $actor_id = get_current_user_id();
799 - $id = desktop_mode_folder_share_invite(
949 + $id = openstation_folder_share_invite(
800 950 $folder_id,
801 951 $actor_id,
802 952 (string) $req->get_param( 'principalType' ),
803 953 (string) $req->get_param( 'principalRef' ),
@@ -805,9 +955,9 @@
805 955 );
806 956 if ( is_wp_error( $id ) ) {
807 957 return $id;
808 958 }
809 - return rest_ensure_response( desktop_mode_files_shape_share( desktop_mode_files_get_share( $id ) ) );
959 + return rest_ensure_response( openstation_files_shape_share( openstation_files_get_share( $id ) ) );
810 960 }
811 961
812 962 /**
813 963 * Verify that the share id in the URL actually belongs to the
@@ -817,20 +967,18 @@
817 967 * mismatched URL never escalates permission — but the routes are
818 968 * hierarchical (`/folders/{id}/shares/{shareId}/…`), so honoring
819 969 * both path segments is the contract callers expect.
820 970 *
821 - * @since 0.8.5
822 - *
823 971 * @param WP_REST_Request $req Request.
824 972 * @return array|WP_Error
825 973 */
826 -function desktop_mode_files_rest_resolve_share_in_folder( WP_REST_Request $req ) {
974 +function openstation_files_rest_resolve_share_in_folder( WP_REST_Request $req ) {
827 975 $folder_id = (int) $req['id'];
828 976 $share_id = (int) $req['shareId'];
829 - $share = desktop_mode_files_get_share( $share_id );
977 + $share = openstation_files_get_share( $share_id );
830 978 if ( ! $share ) {
831 979 return new WP_Error(
832 - 'desktop_mode_files_not_found',
980 + 'openstation_files_not_found',
833 981 __( 'Share not found.', 'desktop-mode' ),
834 982 array( 'status' => 404 )
835 983 );
836 984 }
@@ -835,9 +983,9 @@
835 983 );
836 984 }
837 985 if ( (int) $share['folder_id'] !== $folder_id ) {
838 986 return new WP_Error(
839 - 'desktop_mode_files_not_found',
987 + 'openstation_files_not_found',
840 988 __( 'Share not found in this folder.', 'desktop-mode' ),
841 989 array( 'status' => 404 )
842 990 );
843 991 }
@@ -846,30 +994,30 @@
846 994
847 995 /**
848 996 * PATCH /folders/<id>/shares/<shareId> — owner only.
849 997 */
850 -function desktop_mode_files_rest_update_share( WP_REST_Request $req ) {
851 - $share = desktop_mode_files_rest_resolve_share_in_folder( $req );
998 +function openstation_files_rest_update_share( WP_REST_Request $req ) {
999 + $share = openstation_files_rest_resolve_share_in_folder( $req );
852 1000 if ( is_wp_error( $share ) ) {
853 1001 return $share;
854 1002 }
855 1003 $share_id = (int) $share['id'];
856 - $ok = desktop_mode_folder_share_update_capability( $share_id, get_current_user_id(), (string) $req->get_param( 'capability' ) );
1004 + $ok = openstation_folder_share_update_capability( $share_id, get_current_user_id(), (string) $req->get_param( 'capability' ) );
857 1005 if ( is_wp_error( $ok ) ) {
858 1006 return $ok;
859 1007 }
860 - return rest_ensure_response( desktop_mode_files_shape_share( desktop_mode_files_get_share( $share_id ) ) );
1008 + return rest_ensure_response( openstation_files_shape_share( openstation_files_get_share( $share_id ) ) );
861 1009 }
862 1010
863 1011 /**
864 1012 * DELETE /folders/<id>/shares/<shareId> — owner only.
865 1013 */
866 -function desktop_mode_files_rest_delete_share( WP_REST_Request $req ) {
867 - $share = desktop_mode_files_rest_resolve_share_in_folder( $req );
1014 +function openstation_files_rest_delete_share( WP_REST_Request $req ) {
1015 + $share = openstation_files_rest_resolve_share_in_folder( $req );
868 1016 if ( is_wp_error( $share ) ) {
869 1017 return $share;
870 1018 }
871 - $ok = desktop_mode_folder_share_revoke( (int) $share['id'], get_current_user_id() );
1019 + $ok = openstation_folder_share_revoke( (int) $share['id'], get_current_user_id() );
872 1020 if ( is_wp_error( $ok ) ) {
873 1021 return $ok;
874 1022 }
875 1023 return rest_ensure_response( array( 'deleted' => true ) );
@@ -877,33 +1025,33 @@
877 1025
878 1026 /**
879 1027 * POST /folders/<id>/shares/<shareId>/accept — recipient only.
880 1028 */
881 -function desktop_mode_files_rest_accept_share( WP_REST_Request $req ) {
882 - $share = desktop_mode_files_rest_resolve_share_in_folder( $req );
1029 +function openstation_files_rest_accept_share( WP_REST_Request $req ) {
1030 + $share = openstation_files_rest_resolve_share_in_folder( $req );
883 1031 if ( is_wp_error( $share ) ) {
884 1032 return $share;
885 1033 }
886 - $row = desktop_mode_folder_share_accept( (int) $share['id'], get_current_user_id() );
1034 + $row = openstation_folder_share_accept( (int) $share['id'], get_current_user_id() );
887 1035 if ( is_wp_error( $row ) ) {
888 1036 return $row;
889 1037 }
890 - return rest_ensure_response( desktop_mode_files_shape_share( $row ) );
1038 + return rest_ensure_response( openstation_files_shape_share( $row ) );
891 1039 }
892 1040
893 1041 /**
894 1042 * POST /folders/<id>/shares/<shareId>/deny — recipient only.
895 1043 */
896 -function desktop_mode_files_rest_deny_share( WP_REST_Request $req ) {
897 - $share = desktop_mode_files_rest_resolve_share_in_folder( $req );
1044 +function openstation_files_rest_deny_share( WP_REST_Request $req ) {
1045 + $share = openstation_files_rest_resolve_share_in_folder( $req );
898 1046 if ( is_wp_error( $share ) ) {
899 1047 return $share;
900 1048 }
901 - $row = desktop_mode_folder_share_deny( (int) $share['id'], get_current_user_id() );
1049 + $row = openstation_folder_share_deny( (int) $share['id'], get_current_user_id() );
902 1050 if ( is_wp_error( $row ) ) {
903 1051 return $row;
904 1052 }
905 - return rest_ensure_response( desktop_mode_files_shape_share( $row ) );
1053 + return rest_ensure_response( openstation_files_shape_share( $row ) );
906 1054 }
907 1055
908 1056 /**
909 1057 * POST /folders/<id>/leave — recipient-initiated leave.
@@ -913,11 +1061,11 @@
913 1061 * see the folder (user-principal or role-principal) and removes
914 1062 * their access — for role shares without affecting other role
915 1063 * members, via the per-user decisions table.
916 1064 */
917 -function desktop_mode_files_rest_leave_folder( WP_REST_Request $req ) {
1065 +function openstation_files_rest_leave_folder( WP_REST_Request $req ) {
918 1066 $folder_id = (int) $req['id'];
919 - $ok = desktop_mode_folder_share_leave( $folder_id, get_current_user_id() );
1067 + $ok = openstation_folder_share_leave( $folder_id, get_current_user_id() );
920 1068 if ( is_wp_error( $ok ) ) {
921 1069 return $ok;
922 1070 }
923 1071 return rest_ensure_response( array( 'left' => true ) );
@@ -927,9 +1075,9 @@
927 1075 * POST /files/folder-sharing-tables/purge — destructive cleanup
928 1076 * that drops every table the folder-sharing feature ever created
929 1077 * (current `folder_shares` + `share_user_decisions`, plus any
930 1078 * future variants enumerated via the
931 - * `desktop_mode_files_sharing_tables_for_purge` filter).
1079 + * `openstation_files_sharing_tables_for_purge` filter).
932 1080 *
933 1081 * Restricted to `manage_options` by the permission callback. The
934 1082 * schema-version option is cleared so the next admin-init runs
935 1083 * `install_schema` and recreates the empty tables — keeps the
@@ -934,14 +1082,12 @@
934 1082 * schema-version option is cleared so the next admin-init runs
935 1083 * `install_schema` and recreates the empty tables — keeps the
936 1084 * code path that ASSUMES the tables exist (e.g. heartbeat
937 1085 * delivery queries) working even after a purge.
938 - *
939 - * @since 0.8.5
940 1086 */
941 -function desktop_mode_files_rest_purge_sharing_tables() {
1087 +function openstation_files_rest_purge_sharing_tables() {
942 1088 global $wpdb;
943 - $tables = desktop_mode_files_table_names();
1089 + $tables = openstation_files_table_names();
944 1090
945 1091 $to_drop = array( $tables['shares'], $tables['decisions'] );
946 1092 /**
947 1093 * Filter the list of table names dropped by the
@@ -946,13 +1092,11 @@
946 1092 /**
947 1093 * Filter the list of table names dropped by the
948 1094 * "Delete folder sharing data" admin action.
949 1095 *
950 - * @since 0.8.5
951 - *
952 1096 * @param string[] $tables Default = shares + decisions.
953 1097 */
954 - $to_drop = (array) apply_filters( 'desktop_mode_files_sharing_tables_for_purge', $to_drop );
1098 + $to_drop = (array) apply_filters( 'openstation_files_sharing_tables_for_purge', $to_drop );
955 1099
956 1100 $dropped = array();
957 1101 $skipped = array();
958 1102 $prefix = (string) $wpdb->prefix;
@@ -964,14 +1108,14 @@
964 1108 // Defense-in-depth: a misbehaving filter could push any
965 1109 // string into `$to_drop` and we're about to interpolate
966 1110 // the value directly into a `DROP TABLE` statement (wpdb
967 1111 // has no placeholder for identifiers). Two gates:
968 - // 1. Must match the `[A-Za-z0-9_]+` identifier pattern —
969 - // keeps quotes/backticks/spaces out of the SQL even
970 - // if a filter author smuggled them in.
971 - // 2. Must start with the wpdb prefix — keeps a malicious
972 - // filter from dropping system tables (`wp_users`,
973 - // `wp_options`, …) on a multi-prefix install.
1112 + // 1. Must match the `[A-Za-z0-9_]+` identifier pattern —
1113 + // keeps quotes/backticks/spaces out of the SQL even
1114 + // if a filter author smuggled them in.
1115 + // 2. Must start with the wpdb prefix — keeps a malicious
1116 + // filter from dropping system tables (`wp_users`,
1117 + // `wp_options`, …) on a multi-prefix install.
974 1118 if (
975 1119 ! preg_match( '/^[A-Za-z0-9_]+$/', $tbl ) ||
976 1120 0 !== strpos( $tbl, $prefix )
977 1121 ) {
@@ -989,9 +1133,9 @@
989 1133 // `install_schema` so the tables are recreated empty. Code
990 1134 // paths that JOIN against them (heartbeat, sharing.php
991 1135 // visibility) keep working without a per-request existence
992 1136 // check.
993 - delete_option( DESKTOP_MODE_FILES_SCHEMA_OPTION );
1137 + delete_option( OPENSTATION_FILES_SCHEMA_OPTION );
994 1138
995 1139 /**
996 1140 * Fires after the folder-sharing tables are purged. Plugins
997 1141 * that mirror share state into their own storage can react
@@ -996,35 +1140,33 @@
996 1140 * Fires after the folder-sharing tables are purged. Plugins
997 1141 * that mirror share state into their own storage can react
998 1142 * here.
999 1143 *
1000 - * @since 0.8.5
1001 - *
1002 1144 * @param string[] $dropped Table names that were dropped.
1003 1145 */
1004 - do_action( 'desktop_mode_files_sharing_tables_purged', $dropped );
1146 + do_action( 'openstation_files_sharing_tables_purged', $dropped );
1005 1147
1006 - return rest_ensure_response( array(
1007 - 'dropped' => $dropped,
1008 - 'skipped' => $skipped,
1009 - ) );
1148 + return rest_ensure_response(
1149 + array(
1150 + 'dropped' => $dropped,
1151 + 'skipped' => $skipped,
1152 + )
1153 + );
1010 1154 }
1011 1155
1012 1156 /**
1013 1157 * Permission gate for /users/search. Requires `edit_posts` —
1014 - * `desktop_mode_files_rest_permission` would let any logged-in
1015 - * desktop-mode user pull the directory, which is too broad for an
1158 + * `openstation_files_rest_permission` would let any logged-in
1159 + * openstation user pull the directory, which is too broad for an
1016 1160 * autocomplete that exposes display names + emails.
1017 - *
1018 - * @since 0.8.5
1019 1161 */
1020 -function desktop_mode_files_rest_search_users_permission() {
1021 - $base = desktop_mode_files_rest_permission();
1162 +function openstation_files_rest_search_users_permission() {
1163 + $base = openstation_files_rest_permission();
1022 1164 if ( is_wp_error( $base ) ) {
1023 1165 return $base;
1024 1166 }
1025 1167 if ( ! current_user_can( 'edit_posts' ) ) {
1026 - return new WP_Error( 'desktop_mode_files_forbidden', __( 'You cannot search users.', 'desktop-mode' ), array( 'status' => 403 ) );
1168 + return new WP_Error( 'openstation_files_forbidden', __( 'You cannot search users.', 'desktop-mode' ), array( 'status' => 403 ) );
1027 1169 }
1028 1170 return true;
1029 1171 }
1030 1172
@@ -1030,12 +1172,10 @@
1030 1172
1031 1173 /**
1032 1174 * GET /files/users/search?q=<>&exclude=<csv> — autocomplete for the
1033 1175 * folder share picker.
1034 - *
1035 - * @since 0.8.5
1036 1176 */
1037 -function desktop_mode_files_rest_search_users( WP_REST_Request $req ) {
1177 +function openstation_files_rest_search_users( WP_REST_Request $req ) {
1038 1178 $q = trim( (string) $req->get_param( 'q' ) );
1039 1179 $exclude = array_filter( array_map( 'intval', explode( ',', (string) $req->get_param( 'exclude' ) ) ) );
1040 1180
1041 1181 // Always exclude the current viewer — sharing with yourself is
@@ -1062,14 +1202,12 @@
1062 1202
1063 1203 /**
1064 1204 * Filter the WP_User_Query args used by the share picker.
1065 1205 *
1066 - * @since 0.8.5
1067 - *
1068 1206 * @param array $args Default args.
1069 1207 * @param array $req Request params (`q`, `exclude`).
1070 1208 */
1071 - $args = (array) apply_filters( 'desktop_mode_files_share_user_query_args', $args, $req->get_params() );
1209 + $args = (array) apply_filters( 'openstation_files_share_user_query_args', $args, $req->get_params() );
1072 1210
1073 1211 $query = new WP_User_Query( $args );
1074 1212 $users = $query->get_results();
1075 1213 $out = array();