PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 All 35 releases
← All changes | includes/desktop-files/rest.php +456 -353 0.9.7 → 1.1.11 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,182 +108,274 @@
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 /**
292 380 * Inline the root folder's placements into the boot-time shell
293 381 * config so the desktop file grid hydrates without a REST
@@ -297,61 +385,97 @@
297 385 * client store can't tell the difference; the JS consumer
298 386 * (`src/desktop-files/layer.ts`) consumes the key one-shot, so any
299 387 * later re-hydration still goes through REST for fresh state.
300 388 *
301 - * The `desktop_mode_shell_config` filter only runs while rendering
389 + * The `openstation_shell_config` filter only runs while rendering
302 390 * the shell for an enabled, logged-in user — the same gate the REST
303 391 * permission callback enforces.
304 392 *
305 - * @since 0.9.7
306 - *
307 393 * @param array $config Shell config.
308 394 * @return array
309 395 */
310 -function desktop_mode_files_inject_boot_placements( $config ) {
396 +function openstation_files_inject_boot_placements( $config ) {
311 397 $user_id = get_current_user_id();
312 398 if ( $user_id <= 0 ) {
313 399 return $config;
314 400 }
315 - desktop_mode_files_auto_place_orphans( $user_id );
316 - $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 );
317 403 $out = array();
318 404 foreach ( $rows as $row ) {
319 - $out[] = desktop_mode_files_shape_placement( $row );
405 + $out[] = openstation_files_shape_placement( $row );
320 406 }
321 407 $config['filesBootPlacements'] = $out;
322 408 return $config;
323 409 }
324 -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 );
325 411
326 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 +/**
327 449 * GET /placements
328 450 */
329 -function desktop_mode_files_rest_list_placements( WP_REST_Request $req ) {
451 +function openstation_files_rest_list_placements( WP_REST_Request $req ) {
330 452 $user_id = get_current_user_id();
331 453 $parent_id = (int) $req->get_param( 'folder' );
332 454 // Self-healing backfill — see
333 - // `desktop_mode_files_auto_place_orphan_folders` for the why.
455 + // `openstation_files_auto_place_orphan_folders` for the why.
334 456 // Only runs at the root because that's the only context where
335 457 // auto-placing an orphan folder as a tile is unambiguous.
336 458 if ( 0 === $parent_id ) {
337 - desktop_mode_files_auto_place_orphans( $user_id );
459 + openstation_files_auto_place_orphans( $user_id );
338 460 }
339 - $rows = desktop_mode_files_get_for_user_folder( $user_id, $parent_id );
340 - $out = array();
461 + $rows = openstation_files_get_for_user_folder( $user_id, $parent_id );
462 + $out = array();
341 463 foreach ( $rows as $row ) {
342 - $out[] = desktop_mode_files_shape_placement( $row );
464 + $out[] = openstation_files_shape_placement( $row );
343 465 }
344 - return rest_ensure_response( array(
345 - 'placements' => $out,
346 - 'folderId' => $parent_id,
347 - ) );
466 + return rest_ensure_response(
467 + array(
468 + 'placements' => $out,
469 + 'folderId' => $parent_id,
470 + )
471 + );
348 472 }
349 473
350 474 /**
351 475 * POST /placements
352 476 */
353 -function desktop_mode_files_rest_create_placement( WP_REST_Request $req ) {
477 +function openstation_files_rest_create_placement( WP_REST_Request $req ) {
354 478 $type = (string) $req->get_param( 'type' );
355 479 $ref = (string) $req->get_param( 'ref' );
356 480 $meta = $req->get_param( 'meta' );
357 481
@@ -359,17 +483,17 @@
359 483 // `meta.iconUrl` so the tile renderer can paint it without the
360 484 // browser making a third-party request on every render. Other
361 485 // types skip the resolver entirely (no extra fetch latency).
362 486 if ( 'link' === $type && '' !== $ref ) {
363 - $icon_data_uri = desktop_mode_resolve_favicon( $ref );
487 + $icon_data_uri = openstation_resolve_favicon( $ref );
364 488 if ( is_string( $icon_data_uri ) && '' !== $icon_data_uri ) {
365 - $meta_arr = is_array( $meta ) ? $meta : array();
366 - $meta_arr['iconUrl'] = $icon_data_uri;
367 - $meta = $meta_arr;
489 + $meta_arr = is_array( $meta ) ? $meta : array();
490 + $meta_arr['iconUrl'] = $icon_data_uri;
491 + $meta = $meta_arr;
368 492 }
369 493 }
370 494
371 - $id = desktop_mode_files_place(
495 + $id = openstation_files_place(
372 496 get_current_user_id(),
373 497 (int) $req->get_param( 'parentId' ),
374 498 $type,
375 499 $ref,
@@ -382,43 +506,50 @@
382 506 );
383 507 if ( is_wp_error( $id ) ) {
384 508 return $id;
385 509 }
386 - $row = desktop_mode_files_get_placement( $id );
387 - 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 ) );
388 512 }
389 513
390 514 /**
391 515 * PATCH /placements/<id>
392 516 */
393 -function desktop_mode_files_rest_update_placement( WP_REST_Request $req ) {
517 +function openstation_files_rest_update_placement( WP_REST_Request $req ) {
394 518 $id = (int) $req['id'];
395 - $body = $req->get_json_params() ?: $req->get_params();
396 - $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 );
397 522 if ( ! $current ) {
398 - 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 ) );
399 524 }
400 - $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 );
401 526 if ( is_wp_error( $conflict ) ) {
402 527 return $conflict;
403 528 }
404 529 $changes = array();
405 - 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 ) {
406 537 if ( array_key_exists( $in, $body ) ) {
407 538 $changes[ $col ] = $body[ $in ];
408 539 }
409 540 }
410 - $ok = desktop_mode_files_move( $id, get_current_user_id(), $changes );
541 + $ok = openstation_files_move( $id, get_current_user_id(), $changes );
411 542 if ( is_wp_error( $ok ) ) {
412 543 return $ok;
413 544 }
414 - 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 ) ) );
415 546 }
416 547
417 548 /**
418 549 * DELETE /placements/<id>
419 550 */
420 -function desktop_mode_files_rest_delete_placement( WP_REST_Request $req ) {
551 +function openstation_files_rest_delete_placement( WP_REST_Request $req ) {
421 552 $id = (int) $req['id'];
422 553 $user_id = get_current_user_id();
423 554 // `force=1` query param permanently deletes (purges the row).
424 555 // Default DELETE soft-trashes — the row lands in the recycle
@@ -426,10 +557,10 @@
426 557 // uses on every other resource.
427 558 $force = '1' === (string) $req->get_param( 'force' )
428 559 || true === $req->get_param( 'force' );
429 560 $ok = $force
430 - ? desktop_mode_files_purge_placement( $user_id, $id )
431 - : desktop_mode_files_trash_placement( $user_id, $id );
561 + ? openstation_files_purge_placement( $user_id, $id )
562 + : openstation_files_trash_placement( $user_id, $id );
432 563 if ( is_wp_error( $ok ) ) {
433 564 return $ok;
434 565 }
435 566 return rest_ensure_response(
@@ -442,13 +573,13 @@
442 573
443 574 /**
444 575 * GET /folders
445 576 */
446 -function desktop_mode_files_rest_list_folders() {
447 - $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() );
448 579 $out = array();
449 580 foreach ( $rows as $row ) {
450 - $out[] = desktop_mode_files_shape_folder( $row );
581 + $out[] = openstation_files_shape_folder( $row );
451 582 }
452 583 return rest_ensure_response( array( 'folders' => $out ) );
453 584 }
454 585
@@ -454,10 +585,10 @@
454 585
455 586 /**
456 587 * POST /folders
457 588 */
458 -function desktop_mode_files_rest_create_folder( WP_REST_Request $req ) {
459 - $id = desktop_mode_files_create_folder(
589 +function openstation_files_rest_create_folder( WP_REST_Request $req ) {
590 + $id = openstation_files_create_folder(
460 591 get_current_user_id(),
461 592 array(
462 593 'name' => (string) $req->get_param( 'name' ),
463 594 'share_mode' => (string) $req->get_param( 'shareMode' ),
@@ -466,53 +597,58 @@
466 597 );
467 598 if ( is_wp_error( $id ) ) {
468 599 return $id;
469 600 }
470 - 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 ) ) );
471 602 }
472 603
473 604 /**
474 605 * PATCH /folders/<id>
475 606 */
476 -function desktop_mode_files_rest_update_folder( WP_REST_Request $req ) {
607 +function openstation_files_rest_update_folder( WP_REST_Request $req ) {
477 608 $id = (int) $req['id'];
478 - $body = $req->get_json_params() ?: $req->get_params();
479 - $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 );
480 612 if ( ! $current ) {
481 - 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 ) );
482 614 }
483 - $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 );
484 616 if ( is_wp_error( $conflict ) ) {
485 617 return $conflict;
486 618 }
487 619 $changes = array();
488 - 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 ) {
489 625 if ( array_key_exists( $in, $body ) ) {
490 626 $changes[ $col ] = $body[ $in ];
491 627 }
492 628 }
493 - $ok = desktop_mode_files_update_folder( $id, get_current_user_id(), $changes );
629 + $ok = openstation_files_update_folder( $id, get_current_user_id(), $changes );
494 630 if ( is_wp_error( $ok ) ) {
495 631 return $ok;
496 632 }
497 - 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 ) ) );
498 634 }
499 635
500 636 /**
501 637 * DELETE /folders/<id>
502 638 */
503 -function desktop_mode_files_rest_delete_folder( WP_REST_Request $req ) {
639 +function openstation_files_rest_delete_folder( WP_REST_Request $req ) {
504 640 $id = (int) $req['id'];
505 641 $user_id = get_current_user_id();
506 642 $force = '1' === (string) $req->get_param( 'force' )
507 643 || true === $req->get_param( 'force' );
508 644 // Default DELETE soft-trashes the folder + cascades to child
509 - // placements (see `desktop_mode_files_trash_folder`). `force=1`
645 + // placements (see `openstation_files_trash_folder`). `force=1`
510 646 // permanently deletes both the folder row AND every child
511 647 // placement that was trashed via the cascade.
512 648 $ok = $force
513 - ? desktop_mode_files_purge_folder( $user_id, $id )
514 - : desktop_mode_files_trash_folder( $user_id, $id );
649 + ? openstation_files_purge_folder( $user_id, $id )
650 + : openstation_files_trash_folder( $user_id, $id );
515 651 if ( is_wp_error( $ok ) ) {
516 652 return $ok;
517 653 }
518 654 return rest_ensure_response(
@@ -525,9 +661,9 @@
525 661
526 662 /**
527 663 * PUT /associations — replaces the entire user-association map.
528 664 */
529 -function desktop_mode_files_rest_save_associations( WP_REST_Request $req ) {
665 +function openstation_files_rest_save_associations( WP_REST_Request $req ) {
530 666 $assoc = (array) $req->get_param( 'associations' );
531 667 $clean = array();
532 668 foreach ( $assoc as $type => $opener_id ) {
533 669 $type = sanitize_key( (string) $type );
@@ -536,25 +672,25 @@
536 672 continue;
537 673 }
538 674 $clean[ $type ] = $opener_id;
539 675 }
540 - update_user_meta( get_current_user_id(), DESKTOP_MODE_FILE_ASSOCIATIONS_META, $clean );
541 - return rest_ensure_response( array(
542 - 'associations' => desktop_mode_get_user_file_associations( get_current_user_id() ),
543 - ) );
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 + );
544 682 }
545 683
546 684 /**
547 685 * Shape a placement row for the wire — converts snake_case to
548 - * camelCase and merges in the resolved `Desktop_Mode_File`
686 + * camelCase and merges in the resolved `OpenStation_File`
549 687 * shape so the JS side can render without a second fetch.
550 688 *
551 - * @since 0.9.0
552 - *
553 689 * @param array|null $row Normalized placement row.
554 690 * @return array
555 691 */
556 -function desktop_mode_files_shape_placement( $row ) {
692 +function openstation_files_shape_placement( $row ) {
557 693 if ( ! is_array( $row ) ) {
558 694 return array();
559 695 }
560 696 // Access-gated rows (shared-folder view, viewer lacks read on
@@ -573,9 +709,9 @@
573 709 'previewUrl' => '',
574 710 'exists' => true,
575 711 );
576 712 } else {
577 - $file = desktop_mode_resolve_file( $row['file_type'], $row['file_ref'] );
713 + $file = openstation_resolve_file( $row['file_type'], $row['file_ref'] );
578 714 $shape = $file ? $file->serialize() : array(
579 715 'type' => $row['file_type'],
580 716 'ref' => $row['file_ref'],
581 717 'title' => '',
@@ -593,70 +729,53 @@
593 729 // user staring at a tile that wouldn't move. Falls back to
594 730 // `false` when the helper isn't loaded (defensive — early-boot
595 731 // REST calls before trash.php is required can't grant permission
596 732 // they don't know about).
597 - $viewer_id = (int) get_current_user_id();
598 - $can_trash = false;
599 - if ( $viewer_id > 0 && function_exists( 'desktop_mode_files_user_can_trash_placement' ) ) {
600 - $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 );
601 737 }
602 738
603 739 return array(
604 - 'id' => (int) $row['id'],
605 - 'parentId' => (int) $row['parent_id'],
606 - 'x' => (int) $row['x'],
607 - 'y' => (int) $row['y'],
608 - 'sortOrder' => (int) $row['sort_order'],
609 - 'updatedAtMs' => (int) $row['updated_at_ms'],
610 - 'meta' => isset( $row['meta'] ) ? $row['meta'] : null,
611 - '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,
612 748 // `accessGated` is true when the viewer can't read the
613 749 // underlying entity but the placement is shown anyway (the
614 750 // shared-folder-view UX). Tile renderer surfaces it as a
615 751 // lock overlay + tooltip; the `file` shape above is redacted.
616 - 'accessGated' => $access_gated,
617 - 'canTrash' => $can_trash,
752 + 'accessGated' => $access_gated,
753 + 'canTrash' => $can_trash,
618 754 );
619 755 }
620 756
621 757 /**
622 - * @since 0.9.0
623 - *
624 758 * @param array|null $row Folder row.
625 759 * @return array
626 760 */
627 -function desktop_mode_files_shape_folder( $row ) {
761 +function openstation_files_shape_folder( $row ) {
628 762 if ( ! is_array( $row ) ) {
629 763 return array();
630 764 }
631 765 $shape = array(
632 - 'id' => (int) $row['id'],
633 - 'ownerId' => (int) $row['owner_id'],
634 - 'name' => (string) $row['name'],
635 - 'shareMode' => (string) $row['share_mode'],
636 - 'shareMeta' => isset( $row['share_meta'] ) ? $row['share_meta'] : null,
637 - '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'],
638 772 );
639 - if ( function_exists( 'desktop_mode_files_get_folder_shares' ) ) {
640 - $shares = desktop_mode_files_get_folder_shares( (int) $row['id'] );
641 - $accepted_count = 0;
642 - $has_all = 'all' === (string) $row['share_mode'];
643 - foreach ( $shares as $s ) {
644 - if ( 'accepted' === $s['state'] ) {
645 - $accepted_count++;
646 - }
647 - }
648 - // `shared` is viewer-agnostic — recipients need it for the
649 - // shared-folder badge. The recipient COUNT is owner-internal
650 - // (the dedicated shares endpoint gates the full roster on
651 - // `share_can_manage`), so only managers get the real number;
652 - // every other viewer sees `0`, keeping the wire shape stable.
653 - $can_manage = function_exists( 'desktop_mode_files_share_can_manage' )
654 - && desktop_mode_files_share_can_manage( (int) $row['id'], get_current_user_id() );
655 - $shape['shareSummary'] = array(
656 - 'shared' => $has_all || $accepted_count > 0,
657 - 'recipientCount' => $can_manage ? $accepted_count + ( $has_all ? 1 : 0 ) : 0,
658 - );
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 );
659 778 }
660 779 return $shape;
661 780 }
662 781
@@ -670,16 +789,14 @@
670 789 * The 409 body carries a structured `data` payload the client
671 790 * surfaces as a toast: `{ reason, actor: { id,name,avatar },
672 791 * current: { parentId, parentName, updatedAtMs } }`.
673 792 *
674 - * @since 0.8.5
675 - *
676 793 * @param int $current_ms Current `updated_at_ms` on the row.
677 794 * @param WP_REST_Request $req Inbound request.
678 795 * @param array $row Normalized row (placement or folder).
679 796 * @return WP_Error|null
680 797 */
681 -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 ) {
682 799 $header = $req->get_header( 'if_match' );
683 800 if ( null === $header || '' === $header ) {
684 801 return null;
685 802 }
@@ -694,9 +811,9 @@
694 811 // a non-shared-write workflow that still happens to be the
695 812 // right person; in shared-write the toast may be slightly
696 813 // misleading for the lifetime of pre-v10 rows. New mutations
697 814 // stamp the column accurately. See
698 - // `desktop_mode_files_ensure_updated_by_column`.
815 + // `openstation_files_ensure_updated_by_column`.
699 816 $actor_id = 0;
700 817 if ( isset( $row['updated_by'] ) && (int) $row['updated_by'] > 0 ) {
701 818 $actor_id = (int) $row['updated_by'];
702 819 } elseif ( isset( $row['owner_id'] ) ) {
@@ -706,9 +823,9 @@
706 823
707 824 $parent_id = isset( $row['parent_id'] ) ? (int) $row['parent_id'] : 0;
708 825 $parent_name = '';
709 826 if ( $parent_id > 0 ) {
710 - $parent_folder = desktop_mode_files_get_folder( $parent_id );
827 + $parent_folder = openstation_files_get_folder( $parent_id );
711 828 $parent_name = $parent_folder ? (string) $parent_folder['name'] : '';
712 829 }
713 830
714 831 $reason = 'parent_changed';
@@ -726,16 +843,16 @@
726 843 // drops the parent id/name, so a write attempt can't be used
727 844 // to enumerate other users' display names or folder names
728 845 // (this check runs BEFORE the store's ownership gate, so the
729 846 // 409 body must not leak what the later 403 would protect).
730 - $viewer_id = (int) get_current_user_id();
731 - $viewer_owns_row = isset( $row['owner_id'] ) && (int) $row['owner_id'] === $viewer_id;
732 - $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;
733 850 if ( ! $viewer_can_see && $parent_id > 0 && isset( $parent_folder ) && $parent_folder ) {
734 851 if ( (int) $parent_folder['owner_id'] === $viewer_id ) {
735 852 $viewer_can_see = true;
736 - } elseif ( function_exists( 'desktop_mode_folder_share_user_capability' ) ) {
737 - $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 );
738 855 }
739 856 }
740 857 $actor_payload = array(
741 858 'id' => $viewer_can_see ? $actor_id : 0,
@@ -743,9 +860,9 @@
743 860 'avatar' => $viewer_can_see && $actor ? get_avatar_url( $actor->ID, array( 'size' => 32 ) ) : '',
744 861 );
745 862
746 863 return new WP_Error(
747 - 'desktop_mode_files_conflict',
864 + 'openstation_files_conflict',
748 865 __( 'This row was changed by another session.', 'desktop-mode' ),
749 866 array(
750 867 'status' => 409,
751 868 'data' => array(
@@ -763,14 +880,12 @@
763 880
764 881 /**
765 882 * Shape a share row for the wire.
766 883 *
767 - * @since 0.8.5
768 - *
769 884 * @param array|null $row Normalized share row.
770 885 * @return array
771 886 */
772 -function desktop_mode_files_shape_share( $row ) {
887 +function openstation_files_shape_share( $row ) {
773 888 if ( ! is_array( $row ) ) {
774 889 return array();
775 890 }
776 891 $shape = array(
@@ -784,15 +899,15 @@
784 899 'invitedAtMs' => (int) $row['invited_at_ms'],
785 900 'decidedAtMs' => isset( $row['decided_at_ms'] ) ? $row['decided_at_ms'] : null,
786 901 );
787 902 if ( 'user' === $row['principal_type'] ) {
788 - $uid = (int) $row['principal_ref'];
789 - $user = $uid > 0 ? get_userdata( $uid ) : null;
903 + $uid = (int) $row['principal_ref'];
904 + $user = $uid > 0 ? get_userdata( $uid ) : null;
790 905 $shape['displayName'] = $user ? $user->display_name : '';
791 906 $shape['avatarUrl'] = $user ? get_avatar_url( $uid, array( 'size' => 48 ) ) : '';
792 907 } else {
793 - $roles = wp_roles();
794 - $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;
795 910 $shape['displayName'] = $info ? translate_user_role( (string) $info['name'] ) : (string) $row['principal_ref'];
796 911 $shape['avatarUrl'] = '';
797 912 }
798 913 return $shape;
@@ -800,22 +915,22 @@
800 915
801 916 /**
802 917 * GET /folders/<id>/shares — owner only.
803 918 */
804 -function desktop_mode_files_rest_list_shares( WP_REST_Request $req ) {
919 +function openstation_files_rest_list_shares( WP_REST_Request $req ) {
805 920 $folder_id = (int) $req['id'];
806 921 $user_id = get_current_user_id();
807 - if ( ! desktop_mode_files_share_can_manage( $folder_id, $user_id ) ) {
808 - 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 ) );
809 924 }
810 - $folder = desktop_mode_files_get_folder( $folder_id );
925 + $folder = openstation_files_get_folder( $folder_id );
811 926 if ( ! $folder ) {
812 - 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 ) );
813 928 }
814 - $rows = desktop_mode_files_get_folder_shares( $folder_id );
815 - $out = array();
929 + $rows = openstation_files_get_folder_shares( $folder_id );
930 + $out = array();
816 931 foreach ( $rows as $row ) {
817 - $out[] = desktop_mode_files_shape_share( $row );
932 + $out[] = openstation_files_shape_share( $row );
818 933 }
819 934 return rest_ensure_response(
820 935 array(
821 936 'shares' => $out,
@@ -827,12 +942,12 @@
827 942
828 943 /**
829 944 * POST /folders/<id>/shares — owner only.
830 945 */
831 -function desktop_mode_files_rest_create_share( WP_REST_Request $req ) {
946 +function openstation_files_rest_create_share( WP_REST_Request $req ) {
832 947 $folder_id = (int) $req['id'];
833 948 $actor_id = get_current_user_id();
834 - $id = desktop_mode_folder_share_invite(
949 + $id = openstation_folder_share_invite(
835 950 $folder_id,
836 951 $actor_id,
837 952 (string) $req->get_param( 'principalType' ),
838 953 (string) $req->get_param( 'principalRef' ),
@@ -840,9 +955,9 @@
840 955 );
841 956 if ( is_wp_error( $id ) ) {
842 957 return $id;
843 958 }
844 - 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 ) ) );
845 960 }
846 961
847 962 /**
848 963 * Verify that the share id in the URL actually belongs to the
@@ -852,20 +967,18 @@
852 967 * mismatched URL never escalates permission — but the routes are
853 968 * hierarchical (`/folders/{id}/shares/{shareId}/…`), so honoring
854 969 * both path segments is the contract callers expect.
855 970 *
856 - * @since 0.8.5
857 - *
858 971 * @param WP_REST_Request $req Request.
859 972 * @return array|WP_Error
860 973 */
861 -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 ) {
862 975 $folder_id = (int) $req['id'];
863 976 $share_id = (int) $req['shareId'];
864 - $share = desktop_mode_files_get_share( $share_id );
977 + $share = openstation_files_get_share( $share_id );
865 978 if ( ! $share ) {
866 979 return new WP_Error(
867 - 'desktop_mode_files_not_found',
980 + 'openstation_files_not_found',
868 981 __( 'Share not found.', 'desktop-mode' ),
869 982 array( 'status' => 404 )
870 983 );
871 984 }
@@ -870,9 +983,9 @@
870 983 );
871 984 }
872 985 if ( (int) $share['folder_id'] !== $folder_id ) {
873 986 return new WP_Error(
874 - 'desktop_mode_files_not_found',
987 + 'openstation_files_not_found',
875 988 __( 'Share not found in this folder.', 'desktop-mode' ),
876 989 array( 'status' => 404 )
877 990 );
878 991 }
@@ -881,30 +994,30 @@
881 994
882 995 /**
883 996 * PATCH /folders/<id>/shares/<shareId> — owner only.
884 997 */
885 -function desktop_mode_files_rest_update_share( WP_REST_Request $req ) {
886 - $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 );
887 1000 if ( is_wp_error( $share ) ) {
888 1001 return $share;
889 1002 }
890 1003 $share_id = (int) $share['id'];
891 - $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' ) );
892 1005 if ( is_wp_error( $ok ) ) {
893 1006 return $ok;
894 1007 }
895 - 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 ) ) );
896 1009 }
897 1010
898 1011 /**
899 1012 * DELETE /folders/<id>/shares/<shareId> — owner only.
900 1013 */
901 -function desktop_mode_files_rest_delete_share( WP_REST_Request $req ) {
902 - $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 );
903 1016 if ( is_wp_error( $share ) ) {
904 1017 return $share;
905 1018 }
906 - $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() );
907 1020 if ( is_wp_error( $ok ) ) {
908 1021 return $ok;
909 1022 }
910 1023 return rest_ensure_response( array( 'deleted' => true ) );
@@ -912,33 +1025,33 @@
912 1025
913 1026 /**
914 1027 * POST /folders/<id>/shares/<shareId>/accept — recipient only.
915 1028 */
916 -function desktop_mode_files_rest_accept_share( WP_REST_Request $req ) {
917 - $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 );
918 1031 if ( is_wp_error( $share ) ) {
919 1032 return $share;
920 1033 }
921 - $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() );
922 1035 if ( is_wp_error( $row ) ) {
923 1036 return $row;
924 1037 }
925 - return rest_ensure_response( desktop_mode_files_shape_share( $row ) );
1038 + return rest_ensure_response( openstation_files_shape_share( $row ) );
926 1039 }
927 1040
928 1041 /**
929 1042 * POST /folders/<id>/shares/<shareId>/deny — recipient only.
930 1043 */
931 -function desktop_mode_files_rest_deny_share( WP_REST_Request $req ) {
932 - $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 );
933 1046 if ( is_wp_error( $share ) ) {
934 1047 return $share;
935 1048 }
936 - $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() );
937 1050 if ( is_wp_error( $row ) ) {
938 1051 return $row;
939 1052 }
940 - return rest_ensure_response( desktop_mode_files_shape_share( $row ) );
1053 + return rest_ensure_response( openstation_files_shape_share( $row ) );
941 1054 }
942 1055
943 1056 /**
944 1057 * POST /folders/<id>/leave — recipient-initiated leave.
@@ -948,11 +1061,11 @@
948 1061 * see the folder (user-principal or role-principal) and removes
949 1062 * their access — for role shares without affecting other role
950 1063 * members, via the per-user decisions table.
951 1064 */
952 -function desktop_mode_files_rest_leave_folder( WP_REST_Request $req ) {
1065 +function openstation_files_rest_leave_folder( WP_REST_Request $req ) {
953 1066 $folder_id = (int) $req['id'];
954 - $ok = desktop_mode_folder_share_leave( $folder_id, get_current_user_id() );
1067 + $ok = openstation_folder_share_leave( $folder_id, get_current_user_id() );
955 1068 if ( is_wp_error( $ok ) ) {
956 1069 return $ok;
957 1070 }
958 1071 return rest_ensure_response( array( 'left' => true ) );
@@ -962,9 +1075,9 @@
962 1075 * POST /files/folder-sharing-tables/purge — destructive cleanup
963 1076 * that drops every table the folder-sharing feature ever created
964 1077 * (current `folder_shares` + `share_user_decisions`, plus any
965 1078 * future variants enumerated via the
966 - * `desktop_mode_files_sharing_tables_for_purge` filter).
1079 + * `openstation_files_sharing_tables_for_purge` filter).
967 1080 *
968 1081 * Restricted to `manage_options` by the permission callback. The
969 1082 * schema-version option is cleared so the next admin-init runs
970 1083 * `install_schema` and recreates the empty tables — keeps the
@@ -969,14 +1082,12 @@
969 1082 * schema-version option is cleared so the next admin-init runs
970 1083 * `install_schema` and recreates the empty tables — keeps the
971 1084 * code path that ASSUMES the tables exist (e.g. heartbeat
972 1085 * delivery queries) working even after a purge.
973 - *
974 - * @since 0.8.5
975 1086 */
976 -function desktop_mode_files_rest_purge_sharing_tables() {
1087 +function openstation_files_rest_purge_sharing_tables() {
977 1088 global $wpdb;
978 - $tables = desktop_mode_files_table_names();
1089 + $tables = openstation_files_table_names();
979 1090
980 1091 $to_drop = array( $tables['shares'], $tables['decisions'] );
981 1092 /**
982 1093 * Filter the list of table names dropped by the
@@ -981,13 +1092,11 @@
981 1092 /**
982 1093 * Filter the list of table names dropped by the
983 1094 * "Delete folder sharing data" admin action.
984 1095 *
985 - * @since 0.8.5
986 - *
987 1096 * @param string[] $tables Default = shares + decisions.
988 1097 */
989 - $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 );
990 1099
991 1100 $dropped = array();
992 1101 $skipped = array();
993 1102 $prefix = (string) $wpdb->prefix;
@@ -999,14 +1108,14 @@
999 1108 // Defense-in-depth: a misbehaving filter could push any
1000 1109 // string into `$to_drop` and we're about to interpolate
1001 1110 // the value directly into a `DROP TABLE` statement (wpdb
1002 1111 // has no placeholder for identifiers). Two gates:
1003 - // 1. Must match the `[A-Za-z0-9_]+` identifier pattern —
1004 - // keeps quotes/backticks/spaces out of the SQL even
1005 - // if a filter author smuggled them in.
1006 - // 2. Must start with the wpdb prefix — keeps a malicious
1007 - // filter from dropping system tables (`wp_users`,
1008 - // `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.
1009 1118 if (
1010 1119 ! preg_match( '/^[A-Za-z0-9_]+$/', $tbl ) ||
1011 1120 0 !== strpos( $tbl, $prefix )
1012 1121 ) {
@@ -1024,9 +1133,9 @@
1024 1133 // `install_schema` so the tables are recreated empty. Code
1025 1134 // paths that JOIN against them (heartbeat, sharing.php
1026 1135 // visibility) keep working without a per-request existence
1027 1136 // check.
1028 - delete_option( DESKTOP_MODE_FILES_SCHEMA_OPTION );
1137 + delete_option( OPENSTATION_FILES_SCHEMA_OPTION );
1029 1138
1030 1139 /**
1031 1140 * Fires after the folder-sharing tables are purged. Plugins
1032 1141 * that mirror share state into their own storage can react
@@ -1031,35 +1140,33 @@
1031 1140 * Fires after the folder-sharing tables are purged. Plugins
1032 1141 * that mirror share state into their own storage can react
1033 1142 * here.
1034 1143 *
1035 - * @since 0.8.5
1036 - *
1037 1144 * @param string[] $dropped Table names that were dropped.
1038 1145 */
1039 - do_action( 'desktop_mode_files_sharing_tables_purged', $dropped );
1146 + do_action( 'openstation_files_sharing_tables_purged', $dropped );
1040 1147
1041 - return rest_ensure_response( array(
1042 - 'dropped' => $dropped,
1043 - 'skipped' => $skipped,
1044 - ) );
1148 + return rest_ensure_response(
1149 + array(
1150 + 'dropped' => $dropped,
1151 + 'skipped' => $skipped,
1152 + )
1153 + );
1045 1154 }
1046 1155
1047 1156 /**
1048 1157 * Permission gate for /users/search. Requires `edit_posts` —
1049 - * `desktop_mode_files_rest_permission` would let any logged-in
1050 - * 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
1051 1160 * autocomplete that exposes display names + emails.
1052 - *
1053 - * @since 0.8.5
1054 1161 */
1055 -function desktop_mode_files_rest_search_users_permission() {
1056 - $base = desktop_mode_files_rest_permission();
1162 +function openstation_files_rest_search_users_permission() {
1163 + $base = openstation_files_rest_permission();
1057 1164 if ( is_wp_error( $base ) ) {
1058 1165 return $base;
1059 1166 }
1060 1167 if ( ! current_user_can( 'edit_posts' ) ) {
1061 - 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 ) );
1062 1169 }
1063 1170 return true;
1064 1171 }
1065 1172
@@ -1065,12 +1172,10 @@
1065 1172
1066 1173 /**
1067 1174 * GET /files/users/search?q=<>&exclude=<csv> — autocomplete for the
1068 1175 * folder share picker.
1069 - *
1070 - * @since 0.8.5
1071 1176 */
1072 -function desktop_mode_files_rest_search_users( WP_REST_Request $req ) {
1177 +function openstation_files_rest_search_users( WP_REST_Request $req ) {
1073 1178 $q = trim( (string) $req->get_param( 'q' ) );
1074 1179 $exclude = array_filter( array_map( 'intval', explode( ',', (string) $req->get_param( 'exclude' ) ) ) );
1075 1180
1076 1181 // Always exclude the current viewer — sharing with yourself is
@@ -1097,14 +1202,12 @@
1097 1202
1098 1203 /**
1099 1204 * Filter the WP_User_Query args used by the share picker.
1100 1205 *
1101 - * @since 0.8.5
1102 - *
1103 1206 * @param array $args Default args.
1104 1207 * @param array $req Request params (`q`, `exclude`).
1105 1208 */
1106 - $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() );
1107 1210
1108 1211 $query = new WP_User_Query( $args );
1109 1212 $users = $query->get_results();
1110 1213 $out = array();