PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 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 All 36 releases
← All changes | includes/desktop-files/stored-files-store.php +106 -203 0.9.7 → 1.1.12 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — stored-files store (real per-user file storage).
3 + * OpenStation — stored-files store (real per-user file storage).
4 4 *
5 5 * Backs the `upload` file type. One row per uploaded file in
6 6 * `{$wpdb->prefix}desktop_mode_stored_files`; the bytes live flat on
7 7 * disk under `uploads/desktop-mode-files/<owner_id>/<disk_name>`.
@@ -24,12 +24,11 @@
24 24 * "references, not copies" rule: for `upload` placements the
25 25 * placement OWNS the entity. Soft-trash keeps the bytes; when the
26 26 * owner's last placement of a file is permanently removed, the row,
27 27 * the bytes, and every recipient placement are purged (see
28 - * {@see desktop_mode_stored_files_handle_unplaced()}).
28 + * {@see openstation_stored_files_handle_unplaced()}).
29 29 *
30 - * @package WPDesktopMode
31 - * @since 0.9.6
30 + * @package OpenStation
32 31 */
33 32
34 33 defined( 'ABSPATH' ) || exit;
35 34
@@ -36,16 +35,19 @@
36 35 /**
37 36 * Absolute path of the storage base dir (no trailing slash), or of
38 37 * a user's subdirectory when `$user_id` is given. Purely a path
39 38 * computation — nothing is created; see
40 - * {@see desktop_mode_stored_files_ensure_dir()}.
39 + * {@see openstation_stored_files_ensure_dir()}.
41 40 *
42 - * @since 0.9.6
41 + * The `desktop-mode-files` segment is the pre-rebrand spelling and is
42 + * frozen: users' uploaded bytes already live there. Renaming it points
43 + * the plugin at an empty directory while every stored file stays on
44 + * disk, unreachable. The mismatch with the function name is deliberate.
43 45 *
44 46 * @param int $user_id Optional. Owner whose subdirectory to return.
45 47 * @return string
46 48 */
47 -function desktop_mode_stored_files_dir( $user_id = 0 ) {
49 +function openstation_stored_files_dir( $user_id = 0 ) {
48 50 $uploads = wp_get_upload_dir();
49 51 $base = trailingslashit( $uploads['basedir'] ) . 'desktop-mode-files';
50 52 /**
51 53 * Filters the storage base directory. Sites that can write
@@ -50,13 +52,11 @@
50 52 /**
51 53 * Filters the storage base directory. Sites that can write
52 54 * outside the webroot can point this somewhere safer entirely.
53 55 *
54 - * @since 0.9.6
55 - *
56 56 * @param string $base Absolute path, no trailing slash.
57 57 */
58 - $base = (string) apply_filters( 'desktop_mode_stored_files_base_dir', $base );
58 + $base = (string) apply_filters( 'openstation_stored_files_base_dir', $base );
59 59 if ( (int) $user_id > 0 ) {
60 60 return $base . '/' . (int) $user_id;
61 61 }
62 62 return $base;
@@ -66,22 +66,20 @@
66 66 * Create (idempotently) the storage base + per-user dir and drop
67 67 * the protection files into the base. Returns the user dir path or
68 68 * a `WP_Error` when the filesystem refuses.
69 69 *
70 - * @since 0.9.6
71 - *
72 70 * @param int $user_id Owner.
73 71 * @return string|WP_Error
74 72 */
75 -function desktop_mode_stored_files_ensure_dir( $user_id ) {
73 +function openstation_stored_files_ensure_dir( $user_id ) {
76 74 $user_id = (int) $user_id;
77 75 if ( $user_id <= 0 ) {
78 - return new WP_Error( 'desktop_mode_stored_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) );
76 + return new WP_Error( 'openstation_stored_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) );
79 77 }
80 - $base = desktop_mode_stored_files_dir();
81 - $dir = desktop_mode_stored_files_dir( $user_id );
78 + $base = openstation_stored_files_dir();
79 + $dir = openstation_stored_files_dir( $user_id );
82 80 if ( ! wp_mkdir_p( $dir ) ) {
83 - return new WP_Error( 'desktop_mode_stored_files_mkdir_failed', __( 'Could not create the storage directory.', 'desktop-mode' ), array( 'status' => 500 ) );
81 + return new WP_Error( 'openstation_stored_files_mkdir_failed', __( 'Could not create the storage directory.', 'desktop-mode' ), array( 'status' => 500 ) );
84 82 }
85 83
86 84 // Protection files in the base. `Require all denied` alone 500s
87 85 // on Apache 2.2 and `Deny from all` alone is ignored on pure
@@ -115,14 +113,12 @@
115 113 * Whether `$disk_name` is a well-formed server-generated name
116 114 * (UUID v4, dashes allowed, no dots or separators — nothing a
117 115 * traversal could ride on).
118 116 *
119 - * @since 0.9.6
120 - *
121 117 * @param string $disk_name Candidate.
122 118 * @return bool
123 119 */
124 -function desktop_mode_stored_files_valid_disk_name( $disk_name ) {
120 +function openstation_stored_files_valid_disk_name( $disk_name ) {
125 121 return (bool) preg_match( '/^[a-f0-9-]{16,64}$/', (string) $disk_name );
126 122 }
127 123
128 124 /**
@@ -130,22 +126,20 @@
130 126 * Returns `null` when the row/disk name is malformed or the
131 127 * resolved path escapes the storage base (defense in depth — the
132 128 * disk-name regex should already make escape impossible).
133 129 *
134 - * @since 0.9.6
135 - *
136 130 * @param array $row Normalized stored-file row.
137 131 * @return string|null
138 132 */
139 -function desktop_mode_stored_file_path( $row ) {
133 +function openstation_stored_file_path( $row ) {
140 134 if ( ! is_array( $row ) || empty( $row['owner_id'] ) || empty( $row['disk_name'] ) ) {
141 135 return null;
142 136 }
143 - if ( ! desktop_mode_stored_files_valid_disk_name( $row['disk_name'] ) ) {
137 + if ( ! openstation_stored_files_valid_disk_name( $row['disk_name'] ) ) {
144 138 return null;
145 139 }
146 - $base = desktop_mode_stored_files_dir();
147 - $path = desktop_mode_stored_files_dir( (int) $row['owner_id'] ) . '/' . $row['disk_name'];
140 + $base = openstation_stored_files_dir();
141 + $path = openstation_stored_files_dir( (int) $row['owner_id'] ) . '/' . $row['disk_name'];
148 142
149 143 // realpath() fails on not-yet-existing leaves; canonicalize the
150 144 // parent instead and re-attach the (regex-validated) leaf.
151 145 $real_parent = realpath( dirname( $path ) );
@@ -164,19 +158,45 @@
164 158 /**
165 159 * Insert a stored-file row for bytes ALREADY on disk (the REST
166 160 * intake moves them there via `wp_handle_upload()` first).
167 161 *
168 - * @since 0.9.6
169 - *
170 162 * @param int $owner_id Owner.
171 163 * @param array $args `display_name`, `disk_name`, `size_bytes`, `mime`.
172 164 * @return int|WP_Error Row id.
173 165 */
174 -function desktop_mode_stored_files_create( $owner_id, $args ) {
166 +function openstation_stored_files_create( $owner_id, $args ) {
167 + $id = openstation_stored_files_locked(
168 + static function () use ( $owner_id, $args ) {
169 + return openstation_stored_files_create_locked( $owner_id, $args );
170 + }
171 + );
172 + if ( ! is_wp_error( $id ) ) {
173 + /**
174 + * Fires after a stored-file row is created (bytes are already
175 + * on disk at this point).
176 + *
177 + * @param int $id Stored-file id.
178 + * @param int $owner_id Owner.
179 + */
180 + do_action( 'openstation_stored_file_created', $id, (int) $owner_id );
181 +
182 + }
183 + return $id;
184 +}
185 +
186 +/**
187 + * Insert a stored-file row while holding the reconciliation lock.
188 + *
189 + * @internal
190 + * @param int $owner_id Owner.
191 + * @param array $args Stored-file attributes.
192 + * @return int|WP_Error
193 + */
194 +function openstation_stored_files_create_locked( $owner_id, $args ) {
175 195 global $wpdb;
176 196 $owner_id = (int) $owner_id;
177 197 if ( $owner_id <= 0 ) {
178 - return new WP_Error( 'desktop_mode_stored_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) );
198 + return new WP_Error( 'openstation_stored_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) );
179 199 }
180 200 $args = wp_parse_args(
181 201 $args,
182 202 array(
@@ -185,10 +205,10 @@
185 205 'size_bytes' => 0,
186 206 'mime' => '',
187 207 )
188 208 );
189 - if ( ! desktop_mode_stored_files_valid_disk_name( $args['disk_name'] ) ) {
190 - return new WP_Error( 'desktop_mode_stored_files_bad_disk_name', __( 'Invalid storage name.', 'desktop-mode' ), array( 'status' => 400 ) );
209 + if ( ! openstation_stored_files_valid_disk_name( $args['disk_name'] ) ) {
210 + return new WP_Error( 'openstation_stored_files_bad_disk_name', __( 'Invalid storage name.', 'desktop-mode' ), array( 'status' => 400 ) );
191 211 }
192 212 $display = sanitize_file_name( wp_strip_all_tags( (string) $args['display_name'] ) );
193 213 if ( '' === $display ) {
194 214 $display = __( 'file', 'desktop-mode' );
@@ -193,10 +213,10 @@
193 213 if ( '' === $display ) {
194 214 $display = __( 'file', 'desktop-mode' );
195 215 }
196 216
197 - $tables = desktop_mode_files_table_names();
198 - $now = desktop_mode_files_now_ms();
217 + $tables = openstation_files_table_names();
218 + $now = openstation_files_now_ms();
199 219 $ok = $wpdb->insert(
200 220 $tables['stored_files'],
201 221 array(
202 222 'owner_id' => $owner_id,
@@ -209,23 +229,12 @@
209 229 ),
210 230 array( '%d', '%s', '%s', '%d', '%s', '%d', '%d' )
211 231 );
212 232 if ( false === $ok ) {
213 - return new WP_Error( 'desktop_mode_stored_files_insert_failed', __( 'Failed to record the uploaded file.', 'desktop-mode' ), array( 'status' => 500 ) );
233 + return new WP_Error( 'openstation_stored_files_insert_failed', __( 'Failed to record the uploaded file.', 'desktop-mode' ), array( 'status' => 500 ) );
214 234 }
215 235 $id = (int) $wpdb->insert_id;
216 236
217 - /**
218 - * Fires after a stored-file row is created (bytes are already
219 - * on disk at this point).
220 - *
221 - * @since 0.9.6
222 - *
223 - * @param int $id Stored-file id.
224 - * @param int $owner_id Owner.
225 - */
226 - do_action( 'desktop_mode_stored_file_created', $id, $owner_id );
227 -
228 237 return $id;
229 238 }
230 239
231 240 /**
@@ -230,20 +239,18 @@
230 239
231 240 /**
232 241 * Read one stored-file row.
233 242 *
234 - * @since 0.9.6
235 - *
236 243 * @param int $file_id Row id.
237 244 * @return array|null
238 245 */
239 -function desktop_mode_stored_files_get( $file_id ) {
246 +function openstation_stored_files_get( $file_id ) {
240 247 global $wpdb;
241 248 $file_id = (int) $file_id;
242 249 if ( $file_id <= 0 ) {
243 250 return null;
244 251 }
245 - $tables = desktop_mode_files_table_names();
252 + $tables = openstation_files_table_names();
246 253 $row = $wpdb->get_row(
247 254 $wpdb->prepare( "SELECT * FROM {$tables['stored_files']} WHERE id = %d", $file_id ),
248 255 ARRAY_A
249 256 );
@@ -249,21 +256,20 @@
249 256 );
250 257 if ( ! $row ) {
251 258 return null;
252 259 }
253 - return desktop_mode_stored_files_normalize_row( $row );
260 + return openstation_stored_files_normalize_row( $row );
254 261 }
255 262
256 263 /**
257 264 * Coerce wpdb's stringly-typed row. Internal helper.
258 265 *
259 - * @since 0.9.6
260 266 * @internal
261 267 *
262 268 * @param array $row Raw wpdb row.
263 269 * @return array
264 270 */
265 -function desktop_mode_stored_files_normalize_row( $row ) {
271 +function openstation_stored_files_normalize_row( $row ) {
266 272 return array(
267 273 'id' => (int) $row['id'],
268 274 'owner_id' => (int) $row['owner_id'],
269 275 'display_name' => (string) $row['display_name'],
@@ -279,26 +285,24 @@
279 285 * Rename the display name. The caller enforces WHO may rename
280 286 * (owner-only — see the store gate in `store.php`); this only
281 287 * validates and writes.
282 288 *
283 - * @since 0.9.6
284 - *
285 289 * @param int $file_id Row id.
286 290 * @param string $name New display name.
287 291 * @return true|WP_Error
288 292 */
289 -function desktop_mode_stored_files_rename( $file_id, $name ) {
293 +function openstation_stored_files_rename( $file_id, $name ) {
290 294 global $wpdb;
291 - $row = desktop_mode_stored_files_get( $file_id );
295 + $row = openstation_stored_files_get( $file_id );
292 296 if ( ! $row ) {
293 - return new WP_Error( 'desktop_mode_stored_files_not_found', __( 'Stored file not found.', 'desktop-mode' ), array( 'status' => 404 ) );
297 + return new WP_Error( 'openstation_stored_files_not_found', __( 'Stored file not found.', 'desktop-mode' ), array( 'status' => 404 ) );
294 298 }
295 299 $name = sanitize_file_name( wp_strip_all_tags( (string) $name ) );
296 300 if ( '' === $name ) {
297 - return new WP_Error( 'desktop_mode_stored_files_bad_name', __( 'A file name is required.', 'desktop-mode' ), array( 'status' => 400 ) );
301 + return new WP_Error( 'openstation_stored_files_bad_name', __( 'A file name is required.', 'desktop-mode' ), array( 'status' => 400 ) );
298 302 }
299 - $tables = desktop_mode_files_table_names();
300 - $now = desktop_mode_files_now_ms();
303 + $tables = openstation_files_table_names();
304 + $now = openstation_files_now_ms();
301 305 $wpdb->update(
302 306 $tables['stored_files'],
303 307 array(
304 308 'display_name' => $name,
@@ -325,15 +329,13 @@
325 329
326 330 /**
327 331 * Fires after a stored file is renamed.
328 332 *
329 - * @since 0.9.6
330 - *
331 333 * @param int $file_id Stored-file id.
332 334 * @param string $new_name New display name.
333 335 * @param string $old_name Previous display name.
334 336 */
335 - do_action( 'desktop_mode_stored_file_renamed', (int) $row['id'], $name, (string) $row['display_name'] );
337 + do_action( 'openstation_stored_file_renamed', (int) $row['id'], $name, (string) $row['display_name'] );
336 338
337 339 return true;
338 340 }
339 341
@@ -343,59 +345,53 @@
343 345 * owner. Folder write-collaborators are read + download on
344 346 * uploads; the `canTrash` shape flag, the trash flow, and the
345 347 * recycle-bin drop target all consult this same filter.
346 348 *
347 - * @since 0.9.6
348 - *
349 349 * @param bool $can Decision so far.
350 350 * @param int $user_id Acting user.
351 351 * @param array $row Placement row.
352 352 * @return bool
353 353 */
354 -function desktop_mode_stored_files_gate_trash( $can, $user_id, $row ) {
354 +function openstation_stored_files_gate_trash( $can, $user_id, $row ) {
355 355 if ( ! is_array( $row ) || 'upload' !== (string) ( $row['file_type'] ?? '' ) ) {
356 356 return $can;
357 357 }
358 - $stored = desktop_mode_stored_files_get( (int) $row['file_ref'] );
358 + $stored = openstation_stored_files_get( (int) $row['file_ref'] );
359 359 if ( ! $stored ) {
360 360 return $can; // Dangling tile — normal rules, so it stays cleanable.
361 361 }
362 362 return (int) $stored['owner_id'] === (int) $user_id;
363 363 }
364 -add_filter( 'desktop_mode_files_user_can_trash_placement', 'desktop_mode_stored_files_gate_trash', 20, 3 );
364 +add_filter( 'openstation_files_user_can_trash_placement', 'openstation_stored_files_gate_trash', 20, 3 );
365 365
366 366 /**
367 367 * Delete a stored file: bytes first, then the row. Does NOT touch
368 368 * placements — callers that need the full cascade go through
369 - * {@see desktop_mode_stored_files_purge()}.
369 + * {@see openstation_stored_files_purge()}.
370 370 *
371 - * @since 0.9.6
372 - *
373 371 * @param int $file_id Row id.
374 372 * @return true|WP_Error
375 373 */
376 -function desktop_mode_stored_files_delete( $file_id ) {
374 +function openstation_stored_files_delete( $file_id ) {
377 375 global $wpdb;
378 - $row = desktop_mode_stored_files_get( $file_id );
376 + $row = openstation_stored_files_get( $file_id );
379 377 if ( ! $row ) {
380 - return new WP_Error( 'desktop_mode_stored_files_not_found', __( 'Stored file not found.', 'desktop-mode' ), array( 'status' => 404 ) );
378 + return new WP_Error( 'openstation_stored_files_not_found', __( 'Stored file not found.', 'desktop-mode' ), array( 'status' => 404 ) );
381 379 }
382 - $path = desktop_mode_stored_file_path( $row );
380 + $path = openstation_stored_file_path( $row );
383 381 if ( $path && file_exists( $path ) ) {
384 382 wp_delete_file( $path );
385 383 }
386 - $tables = desktop_mode_files_table_names();
384 + $tables = openstation_files_table_names();
387 385 $wpdb->delete( $tables['stored_files'], array( 'id' => (int) $row['id'] ), array( '%d' ) );
388 386
389 387 /**
390 388 * Fires after a stored file (row + bytes) is deleted.
391 389 *
392 - * @since 0.9.6
393 - *
394 390 * @param int $file_id Stored-file id.
395 391 * @param array $row The row as it was before deletion.
396 392 */
397 - do_action( 'desktop_mode_stored_file_deleted', (int) $row['id'], $row );
393 + do_action( 'openstation_stored_file_deleted', (int) $row['id'], $row );
398 394
399 395 return true;
400 396 }
401 397
@@ -403,21 +399,19 @@
403 399 * Full purge: delete the bytes, the row, every remaining placement
404 400 * of the file (each with a tombstone so heartbeat scrubs recipient
405 401 * tiles live), and every `target_type='file'` share row.
406 402 *
407 - * @since 0.9.6
408 - *
409 403 * @param int $file_id Row id.
410 404 * @return true|WP_Error
411 405 */
412 -function desktop_mode_stored_files_purge( $file_id ) {
406 +function openstation_stored_files_purge( $file_id ) {
413 407 global $wpdb;
414 408 $file_id = (int) $file_id;
415 - $row = desktop_mode_stored_files_get( $file_id );
409 + $row = openstation_stored_files_get( $file_id );
416 410 if ( ! $row ) {
417 - return new WP_Error( 'desktop_mode_stored_files_not_found', __( 'Stored file not found.', 'desktop-mode' ), array( 'status' => 404 ) );
411 + return new WP_Error( 'openstation_stored_files_not_found', __( 'Stored file not found.', 'desktop-mode' ), array( 'status' => 404 ) );
418 412 }
419 - $tables = desktop_mode_files_table_names();
413 + $tables = openstation_files_table_names();
420 414
421 415 // Remaining placements (trashed included — the file is going
422 416 // away for good, a recycle-bin restore must not resurrect a
423 417 // tile pointing at deleted bytes).
@@ -430,9 +424,9 @@
430 424 )
431 425 );
432 426 foreach ( (array) $placement_ids as $pid ) {
433 427 $wpdb->delete( $tables['placements'], array( 'id' => (int) $pid ), array( '%d' ) );
434 - desktop_mode_files_write_tombstone( 'placement', (int) $pid );
428 + openstation_files_write_tombstone( 'placement', (int) $pid );
435 429 }
436 430
437 431 // File shares (target_type='file'). The shares table keys the
438 432 // target id on the historically-named `folder_id` column.
@@ -444,22 +438,20 @@
444 438 ),
445 439 array( '%s', '%d' )
446 440 );
447 441
448 - return desktop_mode_stored_files_delete( $file_id );
442 + return openstation_stored_files_delete( $file_id );
449 443 }
450 444
451 445 /**
452 446 * Sum of stored bytes for one owner.
453 447 *
454 - * @since 0.9.6
455 - *
456 448 * @param int $owner_id Owner.
457 449 * @return int
458 450 */
459 -function desktop_mode_stored_files_total_bytes( $owner_id ) {
451 +function openstation_stored_files_total_bytes( $owner_id ) {
460 452 global $wpdb;
461 - $tables = desktop_mode_files_table_names();
453 + $tables = openstation_files_table_names();
462 454 return (int) $wpdb->get_var(
463 455 $wpdb->prepare(
464 456 "SELECT COALESCE( SUM( size_bytes ), 0 ) FROM {$tables['stored_files']} WHERE owner_id = %d",
465 457 (int) $owner_id
@@ -471,24 +463,20 @@
471 463 * Per-user quota in bytes. 0 = unlimited (the default). Sites
472 464 * enforce a cap via the filter; the REST intake consults this
473 465 * before accepting a new file.
474 466 *
475 - * @since 0.9.6
476 - *
477 467 * @param int $user_id User.
478 468 * @return int
479 469 */
480 -function desktop_mode_stored_files_user_quota_bytes( $user_id ) {
470 +function openstation_stored_files_user_quota_bytes( $user_id ) {
481 471 /**
482 472 * Filters the per-user storage quota in bytes. Return 0 for
483 473 * unlimited.
484 474 *
485 - * @since 0.9.6
486 - *
487 475 * @param int $quota Quota in bytes. Default 0 (unlimited).
488 476 * @param int $user_id User being checked.
489 477 */
490 - return max( 0, (int) apply_filters( 'desktop_mode_stored_files_user_quota_bytes', 0, (int) $user_id ) );
478 + return max( 0, (int) apply_filters( 'openstation_stored_files_user_quota_bytes', 0, (int) $user_id ) );
491 479 }
492 480
493 481 /**
494 482 * Capability required to upload. Defaults to WordPress's own
@@ -494,21 +482,17 @@
494 482 * Capability required to upload. Defaults to WordPress's own
495 483 * `upload_files`; sites that want desktop storage for lower-cap
496 484 * roles loosen via the filter.
497 485 *
498 - * @since 0.9.6
499 - *
500 486 * @return string
501 487 */
502 -function desktop_mode_stored_files_upload_capability() {
488 +function openstation_stored_files_upload_capability() {
503 489 /**
504 490 * Filters the capability required to upload desktop files.
505 491 *
506 - * @since 0.9.6
507 - *
508 492 * @param string $capability Default 'upload_files'.
509 493 */
510 - return (string) apply_filters( 'desktop_mode_stored_files_upload_capability', 'upload_files' );
494 + return (string) apply_filters( 'openstation_stored_files_upload_capability', 'upload_files' );
511 495 }
512 496
513 497 /**
514 498 * Access resolver: can `$user_id` read (view / download) this
@@ -519,15 +503,13 @@
519 503 * - A user with at least read capability on any folder that
520 504 * contains a live placement of the file can (shared-folder
521 505 * contents are visible to the folder's audience).
522 506 *
523 - * @since 0.9.6
524 - *
525 507 * @param int $file_id Stored-file id.
526 508 * @param int $user_id Viewer.
527 509 * @return bool
528 510 */
529 -function desktop_mode_stored_file_user_can_read( $file_id, $user_id ) {
511 +function openstation_stored_file_user_can_read( $file_id, $user_id ) {
530 512 global $wpdb;
531 513 $file_id = (int) $file_id;
532 514 $user_id = (int) $user_id;
533 515 if ( $file_id <= 0 || $user_id <= 0 ) {
@@ -532,9 +514,9 @@
532 514 $user_id = (int) $user_id;
533 515 if ( $file_id <= 0 || $user_id <= 0 ) {
534 516 return false;
535 517 }
536 - $row = desktop_mode_stored_files_get( $file_id );
518 + $row = openstation_stored_files_get( $file_id );
537 519 if ( ! $row ) {
538 520 return false;
539 521 }
540 522 if ( (int) $row['owner_id'] === $user_id ) {
@@ -541,16 +523,16 @@
541 523 return true;
542 524 }
543 525
544 526 // Accepted direct file share.
545 - if ( function_exists( 'desktop_mode_stored_file_share_state' ) ) {
546 - if ( 'accepted' === desktop_mode_stored_file_share_state( $file_id, $user_id ) ) {
527 + if ( function_exists( 'openstation_stored_file_share_state' ) ) {
528 + if ( 'accepted' === openstation_stored_file_share_state( $file_id, $user_id ) ) {
547 529 return true;
548 530 }
549 531 }
550 532
551 533 // Read+ capability on a folder containing a live placement.
552 - $tables = desktop_mode_files_table_names();
534 + $tables = openstation_files_table_names();
553 535 $parents = $wpdb->get_col(
554 536 $wpdb->prepare(
555 537 "SELECT DISTINCT parent_id FROM {$tables['placements']}
556 538 WHERE file_type = %s AND file_ref = %s
@@ -559,11 +541,11 @@
559 541 'upload',
560 542 (string) $file_id
561 543 )
562 544 );
563 - if ( function_exists( 'desktop_mode_folder_share_user_capability' ) ) {
545 + if ( function_exists( 'openstation_folder_share_user_capability' ) ) {
564 546 foreach ( (array) $parents as $parent_id ) {
565 - if ( 'none' !== desktop_mode_folder_share_user_capability( (int) $parent_id, $user_id ) ) {
547 + if ( 'none' !== openstation_folder_share_user_capability( (int) $parent_id, $user_id ) ) {
566 548 return true;
567 549 }
568 550 }
569 551 }
@@ -571,16 +553,14 @@
571 553 /**
572 554 * Last-mile override for stored-file read access. Plugins with
573 555 * their own sharing concepts can widen (or veto) here.
574 556 *
575 - * @since 0.9.6
576 - *
577 557 * @param bool $can Resolved decision so far (false).
578 558 * @param int $file_id Stored-file id.
579 559 * @param int $user_id Viewer.
580 560 * @param array $row Stored-file row.
581 561 */
582 - return (bool) apply_filters( 'desktop_mode_stored_file_can_read', false, $file_id, $user_id, $row );
562 + return (bool) apply_filters( 'openstation_stored_file_can_read', false, $file_id, $user_id, $row );
583 563 }
584 564
585 565 /**
586 566 * Placement-removal listener — the deletion contract.
@@ -592,20 +572,18 @@
592 572 * purge bytes, row, shares, and every recipient placement.
593 573 *
594 574 * Recipient placements going away never delete bytes.
595 575 *
596 - * @since 0.9.6
597 - *
598 576 * @param int $placement_id Removed placement id.
599 577 * @param array $row The removed row.
600 578 */
601 -function desktop_mode_stored_files_handle_unplaced( $placement_id, $row ) {
579 +function openstation_stored_files_handle_unplaced( $placement_id, $row ) {
602 580 global $wpdb;
603 581 if ( ! is_array( $row ) || 'upload' !== (string) ( $row['file_type'] ?? '' ) ) {
604 582 return;
605 583 }
606 584 $file_id = (int) $row['file_ref'];
607 - $file = desktop_mode_stored_files_get( $file_id );
585 + $file = openstation_stored_files_get( $file_id );
608 586 if ( ! $file ) {
609 587 return;
610 588 }
611 589 if ( (int) $row['owner_id'] !== (int) $file['owner_id'] ) {
@@ -610,9 +588,9 @@
610 588 }
611 589 if ( (int) $row['owner_id'] !== (int) $file['owner_id'] ) {
612 590 return; // A recipient's tile went away; bytes stay.
613 591 }
614 - $tables = desktop_mode_files_table_names();
592 + $tables = openstation_files_table_names();
615 593 $remaining = (int) $wpdb->get_var(
616 594 $wpdb->prepare(
617 595 "SELECT COUNT(*) FROM {$tables['placements']}
618 596 WHERE file_type = %s AND file_ref = %s AND owner_id = %d",
@@ -623,109 +601,34 @@
623 601 );
624 602 if ( $remaining > 0 ) {
625 603 return;
626 604 }
627 - desktop_mode_stored_files_purge( $file_id );
605 + openstation_stored_files_purge( $file_id );
628 606 }
629 -add_action( 'desktop_mode_file_unplaced', 'desktop_mode_stored_files_handle_unplaced', 10, 2 );
607 +add_action( 'openstation_file_unplaced', 'openstation_stored_files_handle_unplaced', 10, 2 );
630 608
631 -/**
632 - * Daily reconciliation sweep, both directions:
633 - *
634 - * a) Rows with no placement at all (crashed uploads, interrupted
635 - * purges) older than the grace period → delete row + bytes.
636 - * b) Bytes on disk with no matching row (interrupted deletes)
637 - * whose mtime is older than the grace period → delete bytes.
638 - *
639 - * Rows whose bytes are missing are left alone — `exists()` still
640 - * renders the tile so the user can see and remove it.
641 - *
642 - * @since 0.9.6
643 - */
644 -function desktop_mode_stored_files_reconcile() {
645 - global $wpdb;
646 - $tables = desktop_mode_files_table_names();
647 - $grace = DAY_IN_SECONDS;
648 609
649 - // a) Placement-less rows past grace.
650 - $cutoff_ms = desktop_mode_files_now_ms() - ( $grace * 1000 );
651 - $orphans = $wpdb->get_col(
652 - $wpdb->prepare(
653 - "SELECT sf.id FROM {$tables['stored_files']} sf
654 - LEFT JOIN {$tables['placements']} p
655 - ON p.file_type = 'upload'
656 - AND p.file_ref = CAST( sf.id AS CHAR )
657 - WHERE p.id IS NULL
658 - AND sf.created_at_ms < %d",
659 - $cutoff_ms
660 - )
661 - );
662 - foreach ( (array) $orphans as $orphan_id ) {
663 - desktop_mode_stored_files_delete( (int) $orphan_id );
664 - }
665 610
666 - // b) Row-less bytes past grace. The flat layout makes this a
667 - // two-level scan: <base>/<user_id>/<disk_name>.
668 - $base = desktop_mode_stored_files_dir();
669 - if ( ! is_dir( $base ) ) {
670 - return;
671 - }
672 - $user_dirs = glob( $base . '/*', GLOB_ONLYDIR );
673 - foreach ( (array) $user_dirs as $user_dir ) {
674 - $owner_id = (int) basename( $user_dir );
675 - if ( $owner_id <= 0 ) {
676 - continue;
677 - }
678 - $known = $wpdb->get_col(
679 - $wpdb->prepare(
680 - "SELECT disk_name FROM {$tables['stored_files']} WHERE owner_id = %d",
681 - $owner_id
682 - )
683 - );
684 - $known_set = array_flip( array_map( 'strval', (array) $known ) );
685 - $entries = glob( $user_dir . '/*' );
686 - foreach ( (array) $entries as $entry ) {
687 - $name = basename( $entry );
688 - if ( 'index.php' === $name || ! is_file( $entry ) ) {
689 - continue;
690 - }
691 - if ( isset( $known_set[ $name ] ) ) {
692 - continue;
693 - }
694 - if ( ! desktop_mode_stored_files_valid_disk_name( $name ) ) {
695 - continue; // Not ours — leave foreign files alone.
696 - }
697 - $mtime = (int) filemtime( $entry );
698 - if ( $mtime > 0 && ( time() - $mtime ) > $grace ) {
699 - wp_delete_file( $entry );
700 - }
701 - }
702 - }
703 -}
704 -add_action( 'desktop_mode_files_daily_prune', 'desktop_mode_stored_files_reconcile' );
705 -
706 611 /**
707 612 * When a WordPress user is deleted, purge their stored files (rows,
708 613 * bytes, shares, recipient placements) and remove their directory.
709 614 *
710 - * @since 0.9.6
711 - *
712 615 * @param int $user_id Deleted user id.
713 616 */
714 -function desktop_mode_stored_files_handle_deleted_user( $user_id ) {
617 +function openstation_stored_files_handle_deleted_user( $user_id ) {
715 618 global $wpdb;
716 619 $user_id = (int) $user_id;
717 620 if ( $user_id <= 0 ) {
718 621 return;
719 622 }
720 - $tables = desktop_mode_files_table_names();
623 + $tables = openstation_files_table_names();
721 624 $ids = $wpdb->get_col(
722 625 $wpdb->prepare( "SELECT id FROM {$tables['stored_files']} WHERE owner_id = %d", $user_id )
723 626 );
724 627 foreach ( (array) $ids as $file_id ) {
725 - desktop_mode_stored_files_purge( (int) $file_id );
628 + openstation_stored_files_purge( (int) $file_id );
726 629 }
727 - $dir = desktop_mode_stored_files_dir( $user_id );
630 + $dir = openstation_stored_files_dir( $user_id );
728 631 if ( is_dir( $dir ) ) {
729 632 $index = $dir . '/index.php';
730 633 if ( file_exists( $index ) ) {
731 634 wp_delete_file( $index );
@@ -733,5 +636,5 @@
733 636 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
734 637 @rmdir( $dir ); // Only succeeds when empty — leftovers are the sweep's job.
735 638 }
736 639 }
737 -add_action( 'deleted_user', 'desktop_mode_stored_files_handle_deleted_user' );
640 +add_action( 'deleted_user', 'openstation_stored_files_handle_deleted_user' );