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