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-uploads.php +221 -108 0.9.8 → 1.1.11 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — upload REST intake.
3 + * OpenStation — upload REST intake.
4 4 *
5 5 * One route:
6 6 *
7 7 * POST /desktop-mode/v1/files/uploads
@@ -18,9 +18,9 @@
18 18 * `post_max_size` aggregates. Internally the handler is split into
19 19 * receive (bytes → disk) and register (row + placement) so a future
20 20 * resumable-upload layer can feed the same register step.
21 21 *
22 - * @package WPDesktopMode
22 + * @package OpenStation
23 23 */
24 24
25 25 defined( 'ABSPATH' ) || exit;
26 26
@@ -34,13 +34,26 @@
34 34 * disk name is an extensionless UUID that no handler dispatches.
35 35 *
36 36 * @return string[]
37 37 */
38 -function desktop_mode_stored_files_denied_extensions() {
38 +function openstation_stored_files_denied_extensions() {
39 39 $denied = array(
40 - 'php', 'php3', 'php4', 'php5', 'php7', 'php8',
41 - 'phtml', 'phar', 'pht', 'phps',
42 - 'cgi', 'pl', 'asp', 'aspx', 'jsp', 'shtml',
40 + 'php',
41 + 'php3',
42 + 'php4',
43 + 'php5',
44 + 'php7',
45 + 'php8',
46 + 'phtml',
47 + 'phar',
48 + 'pht',
49 + 'phps',
50 + 'cgi',
51 + 'pl',
52 + 'asp',
53 + 'aspx',
54 + 'jsp',
55 + 'shtml',
43 56 );
44 57 /**
45 58 * Filters the hard-denied extension list. Narrowing this below
46 59 * the shipped set is strongly discouraged.
@@ -46,9 +59,9 @@
46 59 * the shipped set is strongly discouraged.
47 60 *
48 61 * @param string[] $denied Lowercase extensions.
49 62 */
50 - return (array) apply_filters( 'desktop_mode_stored_files_denied_extensions', $denied );
63 + return (array) apply_filters( 'openstation_stored_files_denied_extensions', $denied );
51 64 }
52 65
53 66 /**
54 67 * Whole-filename denylist (dotfiles / server config).
@@ -55,9 +68,9 @@
55 68 *
56 69 * @param string $name Client filename.
57 70 * @return bool True when the name is forbidden.
58 71 */
59 -function desktop_mode_stored_files_is_denied_filename( $name ) {
72 +function openstation_stored_files_is_denied_filename( $name ) {
60 73 $name = strtolower( trim( (string) $name ) );
61 74 if ( in_array( $name, array( '.htaccess', '.user.ini', 'web.config' ), true ) ) {
62 75 return true;
63 76 }
@@ -62,9 +75,9 @@
62 75 return true;
63 76 }
64 77 $segments = explode( '.', $name );
65 78 array_shift( $segments ); // Everything after the first dot is an "extension" segment.
66 - $denied = desktop_mode_stored_files_denied_extensions();
79 + $denied = openstation_stored_files_denied_extensions();
67 80 foreach ( $segments as $segment ) {
68 81 if ( in_array( $segment, $denied, true ) ) {
69 82 return true;
70 83 }
@@ -77,9 +90,9 @@
77 90 *
78 91 * @param int $user_id User.
79 92 * @return int
80 93 */
81 -function desktop_mode_stored_files_max_upload_bytes( $user_id ) {
94 +function openstation_stored_files_max_upload_bytes( $user_id ) {
82 95 $max = (int) wp_max_upload_size();
83 96 /**
84 97 * Filters the per-file upload cap for desktop storage. May only
85 98 * effectively lower it below the server limits — PHP discards
@@ -87,22 +100,22 @@
87 100 *
88 101 * @param int $max Cap in bytes. Default `wp_max_upload_size()`.
89 102 * @param int $user_id User.
90 103 */
91 - return max( 0, (int) apply_filters( 'desktop_mode_stored_files_max_upload_bytes', $max, (int) $user_id ) );
104 + return max( 0, (int) apply_filters( 'openstation_stored_files_max_upload_bytes', $max, (int) $user_id ) );
92 105 }
93 106
94 107 /**
95 108 * Permission: base files gate + the upload capability.
96 109 */
97 -function desktop_mode_files_rest_uploads_permission() {
98 - $base = desktop_mode_files_rest_permission();
110 +function openstation_files_rest_uploads_permission() {
111 + $base = openstation_files_rest_permission();
99 112 if ( is_wp_error( $base ) ) {
100 113 return $base;
101 114 }
102 - if ( ! current_user_can( desktop_mode_stored_files_upload_capability() ) ) {
115 + if ( ! current_user_can( openstation_stored_files_upload_capability() ) ) {
103 116 return new WP_Error(
104 - 'desktop_mode_stored_files_cannot_upload',
117 + 'openstation_stored_files_cannot_upload',
105 118 __( 'You are not allowed to upload files.', 'desktop-mode' ),
106 119 array( 'status' => 403 )
107 120 );
108 121 }
@@ -111,43 +124,78 @@
111 124
112 125 /**
113 126 * Register the upload route.
114 127 */
115 -function desktop_mode_files_register_upload_rest_routes() {
116 - register_rest_route( 'desktop-mode/v1', '/files/uploads', array(
117 - // POST only — PHP parses multipart into $_FILES for real
118 - // POST requests exclusively.
119 - 'methods' => WP_REST_Server::CREATABLE,
120 - 'permission_callback' => 'desktop_mode_files_rest_uploads_permission',
121 - 'callback' => 'desktop_mode_files_rest_upload',
122 - 'args' => array(
123 - 'parentId' => array( 'type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint' ),
124 - 'relativePath' => array( 'type' => 'string', 'default' => '' ),
125 - // No defaults on x/y on purpose: absent coords mean
126 - // "server picks the next free grid slot".
127 - 'x' => array( 'type' => 'integer', 'required' => false ),
128 - 'y' => array( 'type' => 'integer', 'required' => false ),
129 - ),
130 - ) );
128 +function openstation_files_register_upload_rest_routes() {
129 + register_rest_route(
130 + 'desktop-mode/v1',
131 + '/files/uploads',
132 + array(
133 + // POST only — PHP parses multipart into $_FILES for real
134 + // POST requests exclusively.
135 + 'methods' => WP_REST_Server::CREATABLE,
136 + 'permission_callback' => 'openstation_files_rest_uploads_permission',
137 + 'callback' => 'openstation_files_rest_upload',
138 + 'args' => array(
139 + 'parentId' => array(
140 + 'type' => 'integer',
141 + 'default' => 0,
142 + 'sanitize_callback' => 'absint',
143 + ),
144 + 'relativePath' => array(
145 + 'type' => 'string',
146 + 'default' => '',
147 + ),
148 + // No defaults on x/y on purpose: absent coords mean
149 + // "server picks the next free grid slot".
150 + 'x' => array(
151 + 'type' => 'integer',
152 + 'required' => false,
153 + ),
154 + 'y' => array(
155 + 'type' => 'integer',
156 + 'required' => false,
157 + ),
158 + ),
159 + )
160 + );
131 161
132 - register_rest_route( 'desktop-mode/v1', '/files/uploads/paths', array(
133 - 'methods' => WP_REST_Server::CREATABLE,
134 - 'permission_callback' => 'desktop_mode_files_rest_uploads_permission',
135 - 'callback' => 'desktop_mode_files_rest_ensure_upload_path',
136 - 'args' => array(
137 - 'parentId' => array( 'type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint' ),
138 - 'relativePath' => array( 'type' => 'string', 'required' => true ),
139 - ),
140 - ) );
162 + register_rest_route(
163 + 'desktop-mode/v1',
164 + '/files/uploads/paths',
165 + array(
166 + 'methods' => WP_REST_Server::CREATABLE,
167 + 'permission_callback' => 'openstation_files_rest_uploads_permission',
168 + 'callback' => 'openstation_files_rest_ensure_upload_path',
169 + 'args' => array(
170 + 'parentId' => array(
171 + 'type' => 'integer',
172 + 'default' => 0,
173 + 'sanitize_callback' => 'absint',
174 + ),
175 + 'relativePath' => array(
176 + 'type' => 'string',
177 + 'required' => true,
178 + ),
179 + ),
180 + )
181 + );
141 182
142 - register_rest_route( 'desktop-mode/v1', '/files/uploads/(?P<id>\d+)', array(
143 - 'methods' => WP_REST_Server::EDITABLE,
144 - 'permission_callback' => 'desktop_mode_files_rest_permission',
145 - 'callback' => 'desktop_mode_files_rest_rename_upload',
146 - 'args' => array(
147 - 'name' => array( 'type' => 'string', 'required' => true ),
148 - ),
149 - ) );
183 + register_rest_route(
184 + 'desktop-mode/v1',
185 + '/files/uploads/(?P<id>\d+)',
186 + array(
187 + 'methods' => WP_REST_Server::EDITABLE,
188 + 'permission_callback' => 'openstation_files_rest_permission',
189 + 'callback' => 'openstation_files_rest_rename_upload',
190 + 'args' => array(
191 + 'name' => array(
192 + 'type' => 'string',
193 + 'required' => true,
194 + ),
195 + ),
196 + )
197 + );
150 198 }
151 199
152 200 /**
153 201 * POST /files/uploads/paths — mkdir-p a directory path with no
@@ -157,25 +205,32 @@
157 205 *
158 206 * @param WP_REST_Request $req Request.
159 207 * @return WP_REST_Response|WP_Error
160 208 */
161 -function desktop_mode_files_rest_ensure_upload_path( WP_REST_Request $req ) {
209 +function openstation_files_rest_ensure_upload_path( WP_REST_Request $req ) {
162 210 $rel = (string) $req->get_param( 'relativePath' );
163 211 if ( '' === trim( $rel, " \t/" ) ) {
164 - return new WP_Error( 'desktop_mode_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
212 + return new WP_Error( 'openstation_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
165 213 }
166 214 if ( '/' !== substr( $rel, -1 ) ) {
167 215 $rel .= '/'; // Whole string is a directory path.
168 216 }
169 - $folder_id = desktop_mode_files_resolve_relative_path(
217 + $created = array();
218 + $folder_id = openstation_files_resolve_relative_path(
170 219 get_current_user_id(),
171 220 (int) $req->get_param( 'parentId' ),
172 - $rel
221 + $rel,
222 + $created
173 223 );
174 224 if ( is_wp_error( $folder_id ) ) {
175 225 return $folder_id;
176 226 }
177 - return rest_ensure_response( array( 'folderId' => (int) $folder_id ) );
227 + return rest_ensure_response(
228 + array(
229 + 'folderId' => (int) $folder_id,
230 + 'createdFolders' => openstation_files_shape_created_folders( $created ),
231 + )
232 + );
178 233 }
179 234
180 235 /**
181 236 * PATCH /files/uploads/<id> — rename (owner only). Not-found and
@@ -183,20 +238,20 @@
183 238 *
184 239 * @param WP_REST_Request $req Request.
185 240 * @return WP_REST_Response|WP_Error
186 241 */
187 -function desktop_mode_files_rest_rename_upload( WP_REST_Request $req ) {
242 +function openstation_files_rest_rename_upload( WP_REST_Request $req ) {
188 243 $file_id = (int) $req['id'];
189 244 $user_id = get_current_user_id();
190 - $row = desktop_mode_stored_files_get( $file_id );
245 + $row = openstation_stored_files_get( $file_id );
191 246 if ( ! $row || (int) $row['owner_id'] !== $user_id ) {
192 - return desktop_mode_files_download_not_found();
247 + return openstation_files_download_not_found();
193 248 }
194 - $ok = desktop_mode_stored_files_rename( $file_id, (string) $req->get_param( 'name' ) );
249 + $ok = openstation_stored_files_rename( $file_id, (string) $req->get_param( 'name' ) );
195 250 if ( is_wp_error( $ok ) ) {
196 251 return $ok;
197 252 }
198 - $row = desktop_mode_stored_files_get( $file_id );
253 + $row = openstation_stored_files_get( $file_id );
199 254 return rest_ensure_response(
200 255 array(
201 256 'id' => (int) $row['id'],
202 257 'name' => (string) $row['display_name'],
@@ -204,9 +259,9 @@
204 259 'mime' => (string) $row['mime'],
205 260 )
206 261 );
207 262 }
208 -add_action( 'rest_api_init', 'desktop_mode_files_register_upload_rest_routes' );
263 +add_action( 'rest_api_init', 'openstation_files_register_upload_rest_routes' );
209 264
210 265 /**
211 266 * POST /files/uploads
212 267 *
@@ -212,9 +267,9 @@
212 267 *
213 268 * @param WP_REST_Request $req Request.
214 269 * @return WP_REST_Response|WP_Error
215 270 */
216 -function desktop_mode_files_rest_upload( WP_REST_Request $req ) {
271 +function openstation_files_rest_upload( WP_REST_Request $req ) {
217 272 $user_id = get_current_user_id();
218 273 $files = $req->get_file_params();
219 274
220 275 // A body larger than `post_max_size` reaches PHP as a paramless
@@ -224,15 +279,15 @@
224 279 if ( empty( $files ) ) {
225 280 $content_length = isset( $_SERVER['CONTENT_LENGTH'] ) ? (int) $_SERVER['CONTENT_LENGTH'] : 0;
226 281 if ( $content_length > 0 ) {
227 282 return new WP_Error(
228 - 'desktop_mode_stored_files_too_large',
283 + 'openstation_stored_files_too_large',
229 284 __( 'That file is larger than this server accepts.', 'desktop-mode' ),
230 285 array( 'status' => 413 )
231 286 );
232 287 }
233 288 return new WP_Error(
234 - 'desktop_mode_stored_files_no_file',
289 + 'openstation_stored_files_no_file',
235 290 __( 'No file was uploaded.', 'desktop-mode' ),
236 291 array( 'status' => 400 )
237 292 );
238 293 }
@@ -237,15 +292,15 @@
237 292 );
238 293 }
239 294 if ( empty( $files['file'] ) || ! is_array( $files['file'] ) ) {
240 295 return new WP_Error(
241 - 'desktop_mode_stored_files_no_file',
296 + 'openstation_stored_files_no_file',
242 297 __( 'No file was uploaded.', 'desktop-mode' ),
243 298 array( 'status' => 400 )
244 299 );
245 300 }
246 301
247 - $received = desktop_mode_files_upload_receive( $files['file'], $user_id );
302 + $received = openstation_files_upload_receive( $files['file'], $user_id );
248 303 if ( is_wp_error( $received ) ) {
249 304 return $received;
250 305 }
251 306
@@ -257,9 +312,9 @@
257 312 'y' => (int) $y,
258 313 )
259 314 : null;
260 315
261 - $registered = desktop_mode_files_upload_register(
316 + $registered = openstation_files_upload_register(
262 317 $user_id,
263 318 $received,
264 319 (int) $req->get_param( 'parentId' ),
265 320 (string) $req->get_param( 'relativePath' ),
@@ -272,18 +327,53 @@
272 327 }
273 328 return $registered;
274 329 }
275 330
276 - $row = desktop_mode_files_get_placement( $registered['placement_id'] );
331 + $row = openstation_files_get_placement( $registered['placement_id'] );
277 332 return rest_ensure_response(
278 333 array(
279 - 'placement' => desktop_mode_files_shape_placement( $row ),
280 - 'storedFileId' => (int) $registered['file_id'],
334 + 'placement' => openstation_files_shape_placement( $row ),
335 + 'storedFileId' => (int) $registered['file_id'],
336 + 'createdFolders' => openstation_files_shape_created_folders( $registered['created_folders'] ),
281 337 )
282 338 );
283 339 }
284 340
285 341 /**
342 + * Shape the folders a request created mkdir-p style so the client
343 + * can paint their tiles the moment they exist. Each entry carries
344 + * the folder row AND its placement in the parent the client is
345 + * looking at — the per-file `placement` in the same response only
346 + * describes the file inside the leaf folder, which is why the
347 + * wallpaper tile used to wait for the end-of-batch resync (or the
348 + * next Heartbeat delta) after a folder-tree drop.
349 + *
350 + * @internal
351 + *
352 + * @param array $created `{ folder_id, placement_id }` rows from
353 + * `openstation_files_resolve_relative_path()`.
354 + * @return array<int,array{folder:array,placement:array}>
355 + */
356 +function openstation_files_shape_created_folders( $created ) {
357 + $out = array();
358 + foreach ( (array) $created as $entry ) {
359 + if ( empty( $entry['folder_id'] ) || empty( $entry['placement_id'] ) ) {
360 + continue;
361 + }
362 + $folder = openstation_files_get_folder( (int) $entry['folder_id'] );
363 + $placement = openstation_files_get_placement( (int) $entry['placement_id'] );
364 + if ( ! $folder || ! $placement ) {
365 + continue;
366 + }
367 + $out[] = array(
368 + 'folder' => openstation_files_shape_folder( $folder ),
369 + 'placement' => openstation_files_shape_placement( $placement ),
370 + );
371 + }
372 + return $out;
373 +}
374 +
375 +/**
286 376 * Receive step: validate and move the bytes into the owner's
287 377 * storage dir under a fresh UUID disk name. Returns
288 378 * `{ path, disk_name, display_name, size_bytes, mime }` or an
289 379 * error. No DB writes happen here.
@@ -293,15 +383,15 @@
293 383 * @param array $file Single `$_FILES`-shaped entry.
294 384 * @param int $user_id Uploader.
295 385 * @return array|WP_Error
296 386 */
297 -function desktop_mode_files_upload_receive( $file, $user_id ) {
387 +function openstation_files_upload_receive( $file, $user_id ) {
298 388 $user_id = (int) $user_id;
299 389 $client_name = isset( $file['name'] ) ? (string) $file['name'] : '';
300 390
301 - if ( desktop_mode_stored_files_is_denied_filename( $client_name ) ) {
391 + if ( openstation_stored_files_is_denied_filename( $client_name ) ) {
302 392 return new WP_Error(
303 - 'desktop_mode_stored_files_forbidden_type',
393 + 'openstation_stored_files_forbidden_type',
304 394 __( 'This file type is not allowed.', 'desktop-mode' ),
305 395 array( 'status' => 400 )
306 396 );
307 397 }
@@ -306,12 +396,12 @@
306 396 );
307 397 }
308 398
309 399 $size = isset( $file['size'] ) ? (int) $file['size'] : 0;
310 - $max = desktop_mode_stored_files_max_upload_bytes( $user_id );
400 + $max = openstation_stored_files_max_upload_bytes( $user_id );
311 401 if ( $max > 0 && $size > $max ) {
312 402 return new WP_Error(
313 - 'desktop_mode_stored_files_too_large',
403 + 'openstation_stored_files_too_large',
314 404 sprintf(
315 405 /* translators: %s: formatted maximum file size. */
316 406 __( 'That file is larger than the allowed maximum of %s.', 'desktop-mode' ),
317 407 size_format( $max )
@@ -319,18 +409,18 @@
319 409 array( 'status' => 413 )
320 410 );
321 411 }
322 412
323 - $quota = desktop_mode_stored_files_user_quota_bytes( $user_id );
324 - if ( $quota > 0 && ( desktop_mode_stored_files_total_bytes( $user_id ) + $size ) > $quota ) {
413 + $quota = openstation_stored_files_user_quota_bytes( $user_id );
414 + if ( $quota > 0 && ( openstation_stored_files_total_bytes( $user_id ) + $size ) > $quota ) {
325 415 return new WP_Error(
326 - 'desktop_mode_stored_files_quota_exceeded',
416 + 'openstation_stored_files_quota_exceeded',
327 417 __( 'Your desktop storage is full.', 'desktop-mode' ),
328 418 array( 'status' => 403 )
329 419 );
330 420 }
331 421
332 - $dir = desktop_mode_stored_files_ensure_dir( $user_id );
422 + $dir = openstation_stored_files_ensure_dir( $user_id );
333 423 if ( is_wp_error( $dir ) ) {
334 424 return $dir;
335 425 }
336 426
@@ -346,9 +436,9 @@
346 436 * @param array<string,string> $mimes Allowed map.
347 437 * @param int $user_id Uploader.
348 438 */
349 439 $mimes = (array) apply_filters(
350 - 'desktop_mode_stored_files_allowed_mimes',
440 + 'openstation_stored_files_allowed_mimes',
351 441 get_allowed_mime_types( $user_id ),
352 442 $user_id
353 443 );
354 444
@@ -359,13 +449,13 @@
359 449 $name_cb = static function ( $dir_unused, $name_unused, $ext_unused ) use ( $disk_name ) {
360 450 return $disk_name;
361 451 };
362 452 // Resolve the target paths BEFORE hooking `upload_dir` and close
363 - // over plain strings — `desktop_mode_stored_files_dir()` calls
453 + // over plain strings — `openstation_stored_files_dir()` calls
364 454 // `wp_get_upload_dir()`, which applies the `upload_dir` filter,
365 455 // so calling it from inside the closure would recurse infinitely.
366 - $target_base = desktop_mode_stored_files_dir();
367 - $target_dir = desktop_mode_stored_files_dir( $user_id );
456 + $target_base = openstation_stored_files_dir();
457 + $target_dir = openstation_stored_files_dir( $user_id );
368 458 $redirect = static function ( $dirs ) use ( $user_id, $target_base, $target_dir ) {
369 459 $dirs['subdir'] = '/' . $user_id;
370 460 $dirs['path'] = $target_dir;
371 461 $dirs['url'] = $dirs['baseurl'] . '/desktop-mode-files/' . $user_id;
@@ -383,9 +473,9 @@
383 473 * @param array $overrides Overrides array.
384 474 * @param int $user_id Uploader.
385 475 */
386 476 $overrides = (array) apply_filters(
387 - 'desktop_mode_stored_files_upload_overrides',
477 + 'openstation_stored_files_upload_overrides',
388 478 array(
389 479 'test_form' => false,
390 480 'mimes' => $mimes,
391 481 'unique_filename_callback' => $name_cb,
@@ -400,9 +490,9 @@
400 490 remove_filter( 'upload_dir', $redirect );
401 491
402 492 if ( isset( $result['error'] ) ) {
403 493 return new WP_Error(
404 - 'desktop_mode_stored_files_upload_failed',
494 + 'openstation_stored_files_upload_failed',
405 495 (string) $result['error'],
406 496 array( 'status' => 400 )
407 497 );
408 498 }
@@ -428,15 +518,20 @@
428 518 * @param string $relative_path Optional `a/b/c.ext` path; directory
429 519 * segments are resolved under `$parent_id`.
430 520 * @param array|null $coords `x`, `y` for the placement, or null
431 521 * to auto-place at the next free slot.
432 - * @return array|WP_Error `{ file_id, placement_id }`.
522 + * @return array|WP_Error `{ file_id, placement_id, created_folders }` —
523 + * `created_folders` lists the `{ folder_id,
524 + * placement_id }` pairs the relative path
525 + * created (empty for flat uploads and for
526 + * segments that already existed).
433 527 */
434 -function desktop_mode_files_upload_register( $user_id, $received, $parent_id, $relative_path = '', $coords = null ) {
528 +function openstation_files_upload_register( $user_id, $received, $parent_id, $relative_path = '', $coords = null ) {
435 529 $parent_id = max( 0, (int) $parent_id );
530 + $created = array();
436 531
437 532 if ( '' !== (string) $relative_path ) {
438 - $resolved = desktop_mode_files_resolve_relative_path( (int) $user_id, $parent_id, (string) $relative_path );
533 + $resolved = openstation_files_resolve_relative_path( (int) $user_id, $parent_id, (string) $relative_path, $created );
439 534 if ( is_wp_error( $resolved ) ) {
440 535 return $resolved;
441 536 }
442 537 $parent_id = $resolved;
@@ -441,9 +536,9 @@
441 536 }
442 537 $parent_id = $resolved;
443 538 }
444 539
445 - $file_id = desktop_mode_stored_files_create(
540 + $file_id = openstation_stored_files_create(
446 541 (int) $user_id,
447 542 array(
448 543 'display_name' => $received['display_name'],
449 544 'disk_name' => $received['disk_name'],
@@ -455,9 +550,9 @@
455 550 return $file_id;
456 551 }
457 552
458 553 if ( is_array( $coords ) && isset( $coords['x'], $coords['y'] ) ) {
459 - $placement_id = desktop_mode_files_place(
554 + $placement_id = openstation_files_place(
460 555 (int) $user_id,
461 556 $parent_id,
462 557 'upload',
463 558 (string) $file_id,
@@ -469,9 +564,9 @@
469 564 } else {
470 565 // No coords sent (folder-tree members, batch files after
471 566 // the first) — the server picks the next free grid slot so
472 567 // tiles never stack at the origin.
473 - $placement_id = desktop_mode_files_place_at_next_free_slot(
568 + $placement_id = openstation_files_place_at_next_free_slot(
474 569 (int) $user_id,
475 570 $parent_id,
476 571 'upload',
477 572 (string) $file_id
@@ -478,12 +573,12 @@
478 573 );
479 574 }
480 575 if ( is_wp_error( $placement_id ) ) {
481 576 // Roll back through the store primitive so the documented
482 - // `desktop_mode_stored_file_created` / `_deleted` action pair
577 + // `openstation_stored_file_created` / `_deleted` action pair
483 578 // stays balanced for subscribers (and the bytes go with the
484 579 // row — the caller's outer cleanup guard becomes a no-op).
485 - desktop_mode_stored_files_delete( (int) $file_id );
580 + openstation_stored_files_delete( (int) $file_id );
486 581 return $placement_id;
487 582 }
488 583
489 584 /**
@@ -493,13 +588,14 @@
493 588 * @param int $file_id Stored-file id.
494 589 * @param int $placement_id Placement id.
495 590 * @param int $user_id Uploader.
496 591 */
497 - do_action( 'desktop_mode_stored_file_uploaded', (int) $file_id, (int) $placement_id, (int) $user_id );
592 + do_action( 'openstation_stored_file_uploaded', (int) $file_id, (int) $placement_id, (int) $user_id );
498 593
499 594 return array(
500 - 'file_id' => (int) $file_id,
501 - 'placement_id' => (int) $placement_id,
595 + 'file_id' => (int) $file_id,
596 + 'placement_id' => (int) $placement_id,
597 + 'created_folders' => $created,
502 598 );
503 599 }
504 600
505 601 /**
@@ -515,11 +611,18 @@
515 611 * @param int $base_parent_id Folder to resolve under (0 = root).
516 612 * @param string $relative_path `a/b/c.ext` or `a/b/` (trailing
517 613 * slash = pure directory path, e.g.
518 614 * an empty folder from a drag).
615 + * @param array $created Optional, by reference. Receives one
616 + * `{ folder_id, placement_id }` entry per
617 + * folder this call created, outermost
618 + * first, so the caller can hand the new
619 + * tiles to the client in the same
620 + * response. Reused segments are not
621 + * listed.
519 622 * @return int|WP_Error Folder id to place the file in.
520 623 */
521 -function desktop_mode_files_resolve_relative_path( $user_id, $base_parent_id, $relative_path ) {
624 +function openstation_files_resolve_relative_path( $user_id, $base_parent_id, $relative_path, &$created = null ) {
522 625 global $wpdb;
523 626 $user_id = (int) $user_id;
524 627 $parent_id = max( 0, (int) $base_parent_id );
525 628 $path = str_replace( '\\', '/', (string) $relative_path );
@@ -524,9 +627,9 @@
524 627 $parent_id = max( 0, (int) $base_parent_id );
525 628 $path = str_replace( '\\', '/', (string) $relative_path );
526 629
527 630 if ( false !== strpos( $path, "\0" ) ) {
528 - return new WP_Error( 'desktop_mode_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
631 + return new WP_Error( 'openstation_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
529 632 }
530 633
531 634 $is_dir_path = '/' === substr( $path, -1 );
532 635 $segments = array_values( array_filter( explode( '/', $path ), 'strlen' ) );
@@ -536,19 +639,19 @@
536 639 if ( empty( $segments ) ) {
537 640 return $parent_id;
538 641 }
539 642 if ( count( $segments ) > 32 ) {
540 - return new WP_Error( 'desktop_mode_stored_files_path_too_deep', __( 'That folder tree is nested too deeply.', 'desktop-mode' ), array( 'status' => 400 ) );
643 + return new WP_Error( 'openstation_stored_files_path_too_deep', __( 'That folder tree is nested too deeply.', 'desktop-mode' ), array( 'status' => 400 ) );
541 644 }
542 645
543 - $tables = desktop_mode_files_table_names();
646 + $tables = openstation_files_table_names();
544 647 foreach ( $segments as $segment ) {
545 648 if ( '.' === $segment || '..' === $segment ) {
546 - return new WP_Error( 'desktop_mode_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
649 + return new WP_Error( 'openstation_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
547 650 }
548 651 $name = sanitize_file_name( wp_strip_all_tags( $segment ) );
549 652 if ( '' === $name ) {
550 - return new WP_Error( 'desktop_mode_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
653 + return new WP_Error( 'openstation_stored_files_bad_path', __( 'Invalid path.', 'desktop-mode' ), array( 'status' => 400 ) );
551 654 }
552 655
553 656 // Dedupe: a live folder of this name, placed in this parent,
554 657 // owned by the acting user.
@@ -575,16 +678,26 @@
575 678 $parent_id = (int) $existing;
576 679 continue;
577 680 }
578 681
579 - $folder_id = desktop_mode_files_create_folder( $user_id, array( 'name' => $name ) );
682 + $folder_id = openstation_files_create_folder( $user_id, array( 'name' => $name ) );
580 683 if ( is_wp_error( $folder_id ) ) {
581 684 return $folder_id;
582 685 }
583 - $placement = desktop_mode_files_place( $user_id, $parent_id, 'folder', (string) $folder_id );
686 + // Next free slot, same as the file placements below: a bare
687 + // `openstation_files_place()` pins the tile at 0,0, which the
688 + // layer only displaces client-side — the stored coordinates
689 + // stay wrong and the tile jumps on the next repaint.
690 + $placement = openstation_files_place_at_next_free_slot( $user_id, $parent_id, 'folder', (string) $folder_id );
584 691 if ( is_wp_error( $placement ) ) {
585 692 return $placement;
586 693 }
694 + if ( is_array( $created ) ) {
695 + $created[] = array(
696 + 'folder_id' => (int) $folder_id,
697 + 'placement_id' => (int) $placement,
698 + );
699 + }
587 700 $parent_id = (int) $folder_id;
588 701 }
589 702 return $parent_id;
590 703 }
@@ -595,15 +708,15 @@
595 708 *
596 709 * @param array $config Shell config.
597 710 * @return array
598 711 */
599 -function desktop_mode_stored_files_inject_shell_config( $config ) {
600 - $user_id = get_current_user_id();
712 +function openstation_stored_files_inject_shell_config( $config ) {
713 + $user_id = get_current_user_id();
601 714 $config['desktopStorage'] = array(
602 - 'canUpload' => $user_id > 0 && current_user_can( desktop_mode_stored_files_upload_capability() ),
603 - 'maxBytes' => desktop_mode_stored_files_max_upload_bytes( $user_id ),
604 - 'quotaBytes' => desktop_mode_stored_files_user_quota_bytes( $user_id ),
715 + 'canUpload' => $user_id > 0 && current_user_can( openstation_stored_files_upload_capability() ),
716 + 'maxBytes' => openstation_stored_files_max_upload_bytes( $user_id ),
717 + 'quotaBytes' => openstation_stored_files_user_quota_bytes( $user_id ),
605 718 'zipAvailable' => class_exists( 'ZipArchive' ),
606 719 );
607 720 return $config;
608 721 }
609 -add_filter( 'desktop_mode_shell_config', 'desktop_mode_stored_files_inject_shell_config', 20 );
722 +add_filter( 'openstation_shell_config', 'openstation_stored_files_inject_shell_config', 20 );