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/folders-store.php +128 -132 0.9.5 → 1.1.11 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Folders store.
3 + * OpenStation — Folders store.
4 4 *
5 5 * CRUD primitives for the `_desktop_mode_folders` table. Folders
6 6 * are first-class files: they have an owner, a name, a share
7 7 * mode, and a JSON `share_meta` column carrying the user / role
@@ -7,20 +7,19 @@
7 7 * mode, and a JSON `share_meta` column carrying the user / role
8 8 * lists when `share_mode` is `users` or `roles`.
9 9 *
10 10 * Visibility beyond the owner is computed by sharing.php, which
11 - * hooks the `desktop_mode_files_visible_folders` filter at
11 + * hooks the `openstation_files_visible_folders` filter at
12 12 * priority 5 to merge accepted shares and `share_mode='all'`
13 13 * folders onto the owner's list.
14 14 *
15 - * @package WPDesktopMode
16 - * @since 0.9.0
15 + * @package OpenStation
17 16 */
18 17
19 18 defined( 'ABSPATH' ) || exit;
20 19
21 20 /** Allowed share-mode values. */
22 -function desktop_mode_files_share_modes() {
21 +function openstation_files_share_modes() {
23 22 $modes = array( 'private', 'users', 'roles', 'all' );
24 23 /**
25 24 * Filter the allowed `share_mode` values. Plugins can add
26 25 * (e.g. 'team') by registering both the value here and a
@@ -25,51 +24,57 @@
25 24 * Filter the allowed `share_mode` values. Plugins can add
26 25 * (e.g. 'team') by registering both the value here and a
27 26 * matching visibility callback.
28 27 *
29 - * @since 0.9.0
30 - *
31 28 * @param string[] $modes Default modes.
32 29 */
33 - return (array) apply_filters( 'desktop_mode_files_share_modes', $modes );
30 + return (array) apply_filters( 'openstation_files_share_modes', $modes );
34 31 }
35 32
36 33 /**
37 34 * Create a folder.
38 35 *
39 - * @since 0.9.0
40 - *
41 36 * @param int $owner_id Owner.
42 37 * @param array $args `name`, `share_mode`, `share_meta`.
43 38 * @return int|WP_Error Folder id on success.
44 39 */
45 -function desktop_mode_files_create_folder( $owner_id, $args = array() ) {
40 +function openstation_files_create_folder( $owner_id, $args = array() ) {
46 41 global $wpdb;
47 42
48 43 $owner_id = (int) $owner_id;
49 44 if ( $owner_id <= 0 ) {
50 - return new WP_Error( 'desktop_mode_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) );
45 + return new WP_Error( 'openstation_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) );
51 46 }
52 47
53 - $args = wp_parse_args( $args, array(
54 - 'name' => '',
55 - 'share_mode' => 'private',
56 - 'share_meta' => null,
57 - ) );
48 + $args = wp_parse_args(
49 + $args,
50 + array(
51 + 'name' => '',
52 + 'share_mode' => 'private',
53 + 'share_meta' => null,
54 + )
55 + );
58 56
59 57 $name = sanitize_text_field( (string) $args['name'] );
60 58 if ( '' === $name ) {
61 - return new WP_Error( 'desktop_mode_files_missing_name', __( 'Folder name is required.', 'desktop-mode' ), array( 'status' => 400 ) );
59 + return new WP_Error( 'openstation_files_missing_name', __( 'Folder name is required.', 'desktop-mode' ), array( 'status' => 400 ) );
62 60 }
63 61
64 62 $mode = (string) $args['share_mode'];
65 - $modes = desktop_mode_files_share_modes();
63 + $modes = openstation_files_share_modes();
66 64 if ( ! in_array( $mode, $modes, true ) ) {
67 - return new WP_Error( 'desktop_mode_files_invalid_share_mode', __( 'Invalid share mode.', 'desktop-mode' ), array( 'status' => 400, 'mode' => $mode ) );
65 + return new WP_Error(
66 + 'openstation_files_invalid_share_mode',
67 + __( 'Invalid share mode.', 'desktop-mode' ),
68 + array(
69 + 'status' => 400,
70 + 'mode' => $mode,
71 + )
72 + );
68 73 }
69 74
70 - $tables = desktop_mode_files_table_names();
71 - $now = desktop_mode_files_now_ms();
75 + $tables = openstation_files_table_names();
76 + $now = openstation_files_now_ms();
72 77 $row = array(
73 78 'owner_id' => $owner_id,
74 79 'name' => $name,
75 80 'share_mode' => $mode,
@@ -78,9 +83,9 @@
78 83 );
79 84
80 85 $ok = $wpdb->insert( $tables['folders'], $row, array( '%d', '%s', '%s', '%s', '%d' ) );
81 86 if ( false === $ok ) {
82 - return new WP_Error( 'desktop_mode_files_insert_failed', __( 'Failed to create folder.', 'desktop-mode' ), array( 'status' => 500 ) );
87 + return new WP_Error( 'openstation_files_insert_failed', __( 'Failed to create folder.', 'desktop-mode' ), array( 'status' => 500 ) );
83 88 }
84 89 $id = (int) $wpdb->insert_id;
85 90
86 91 $row['id'] = $id;
@@ -87,14 +92,12 @@
87 92
88 93 /**
89 94 * Fires after a folder is created.
90 95 *
91 - * @since 0.9.0
92 - *
93 96 * @param int $id Folder id.
94 97 * @param array $row Inserted row.
95 98 */
96 - do_action( 'desktop_mode_folder_created', $id, $row );
99 + do_action( 'openstation_folder_created', $id, $row );
97 100
98 101 return $id;
99 102 }
100 103
@@ -100,26 +103,24 @@
100 103
101 104 /**
102 105 * Update a folder. Only the owner can update for now.
103 106 *
104 - * @since 0.9.0
105 - *
106 107 * @param int $folder_id Folder id.
107 108 * @param int $user_id Acting user.
108 109 * @param array $changes `name`, `share_mode`, `share_meta`.
109 110 * @return true|WP_Error
110 111 */
111 -function desktop_mode_files_update_folder( $folder_id, $user_id, $changes = array() ) {
112 +function openstation_files_update_folder( $folder_id, $user_id, $changes = array() ) {
112 113 global $wpdb;
113 114
114 115 $folder_id = (int) $folder_id;
115 116 $user_id = (int) $user_id;
116 - $prev = desktop_mode_files_get_folder( $folder_id );
117 + $prev = openstation_files_get_folder( $folder_id );
117 118 if ( ! $prev ) {
118 - return new WP_Error( 'desktop_mode_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
119 + return new WP_Error( 'openstation_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
119 120 }
120 121 if ( (int) $prev['owner_id'] !== $user_id ) {
121 - return new WP_Error( 'desktop_mode_files_forbidden', __( 'You cannot edit this folder.', 'desktop-mode' ), array( 'status' => 403 ) );
122 + return new WP_Error( 'openstation_files_forbidden', __( 'You cannot edit this folder.', 'desktop-mode' ), array( 'status' => 403 ) );
122 123 }
123 124
124 125 $set = array();
125 126 $fmt = array();
@@ -126,9 +127,9 @@
126 127
127 128 if ( isset( $changes['name'] ) ) {
128 129 $name = sanitize_text_field( (string) $changes['name'] );
129 130 if ( '' === $name ) {
130 - return new WP_Error( 'desktop_mode_files_missing_name', __( 'Folder name cannot be empty.', 'desktop-mode' ), array( 'status' => 400 ) );
131 + return new WP_Error( 'openstation_files_missing_name', __( 'Folder name cannot be empty.', 'desktop-mode' ), array( 'status' => 400 ) );
131 132 }
132 133 $set['name'] = $name;
133 134 $fmt[] = '%s';
134 135 }
@@ -133,11 +134,11 @@
133 134 $fmt[] = '%s';
134 135 }
135 136 if ( isset( $changes['share_mode'] ) ) {
136 137 $mode = (string) $changes['share_mode'];
137 - $modes = desktop_mode_files_share_modes();
138 + $modes = openstation_files_share_modes();
138 139 if ( ! in_array( $mode, $modes, true ) ) {
139 - return new WP_Error( 'desktop_mode_files_invalid_share_mode', __( 'Invalid share mode.', 'desktop-mode' ), array( 'status' => 400 ) );
140 + return new WP_Error( 'openstation_files_invalid_share_mode', __( 'Invalid share mode.', 'desktop-mode' ), array( 'status' => 400 ) );
140 141 }
141 142 $set['share_mode'] = $mode;
142 143 $fmt[] = '%s';
143 144 }
@@ -148,16 +149,16 @@
148 149 if ( empty( $set ) ) {
149 150 return true;
150 151 }
151 152
152 - $now = desktop_mode_files_now_ms();
153 + $now = openstation_files_now_ms();
153 154 $set['updated_at_ms'] = $now;
154 155 $fmt[] = '%d';
155 156
156 - $tables = desktop_mode_files_table_names();
157 + $tables = openstation_files_table_names();
157 158 $ok = $wpdb->update( $tables['folders'], $set, array( 'id' => $folder_id ), $fmt, array( '%d' ) );
158 159 if ( false === $ok ) {
159 - return new WP_Error( 'desktop_mode_files_update_failed', __( 'Failed to update folder.', 'desktop-mode' ), array( 'status' => 500 ) );
160 + return new WP_Error( 'openstation_files_update_failed', __( 'Failed to update folder.', 'desktop-mode' ), array( 'status' => 500 ) );
160 161 }
161 162
162 163 // Propagate rename to every placement that POINTS AT this folder
163 164 // (file_type='folder', file_ref=folder_id) by bumping their
@@ -180,16 +181,14 @@
180 181 * different `file_type` (e.g. an "alias" placement) can join
181 182 * the propagation by returning a non-null SQL fragment via
182 183 * this filter. Return `null` to opt OUT entirely (rare).
183 184 *
184 - * @since 0.8.5
185 - *
186 185 * @param string $where Default WHERE clause body.
187 186 * @param int $folder_id Folder being renamed.
188 187 * @param int $user_id Acting user (folder owner).
189 188 */
190 189 $where = (string) apply_filters(
191 - 'desktop_mode_folder_rename_bump_where',
190 + 'openstation_folder_rename_bump_where',
192 191 $wpdb->prepare(
193 192 "file_type = 'folder' AND file_ref = %s",
194 193 (string) $folder_id
195 194 ),
@@ -211,10 +210,8 @@
211 210 * placements have been bumped. Subscribers can react (e.g.
212 211 * dispatch their own cross-window broadcasts, refresh sidebar
213 212 * displays of the folder name).
214 213 *
215 - * @since 0.8.5
216 - *
217 214 * @param int $folder_id Folder id.
218 215 * @param string $new_name New name (sanitized).
219 216 * @param string $old_name Previous name.
220 217 * @param int $user_id Acting user (folder owner).
@@ -219,9 +216,9 @@
219 216 * @param string $old_name Previous name.
220 217 * @param int $user_id Acting user (folder owner).
221 218 */
222 219 do_action(
223 - 'desktop_mode_folder_renamed',
220 + 'openstation_folder_renamed',
224 221 $folder_id,
225 222 (string) $set['name'],
226 223 (string) $prev['name'],
227 224 $user_id
@@ -227,34 +224,30 @@
227 224 $user_id
228 225 );
229 226 }
230 227
231 - $next = desktop_mode_files_get_folder( $folder_id );
228 + $next = openstation_files_get_folder( $folder_id );
232 229
233 230 /**
234 231 * Fires after a folder is updated.
235 232 *
236 - * @since 0.9.0
237 - *
238 233 * @param int $id Folder id.
239 234 * @param array $next Row after.
240 235 * @param array $prev Row before.
241 236 */
242 - do_action( 'desktop_mode_folder_updated', $folder_id, $next, $prev );
237 + do_action( 'openstation_folder_updated', $folder_id, $next, $prev );
243 238
244 239 if ( isset( $changes['share_mode'] ) || array_key_exists( 'share_meta', $changes ) ) {
245 240 /**
246 241 * Fires after a folder's share state changes (mode or
247 242 * meta). Plugins listening for sharing events can subscribe
248 - * to this rather than diff `desktop_mode_folder_updated`.
243 + * to this rather than diff `openstation_folder_updated`.
249 244 *
250 - * @since 0.9.0
251 - *
252 245 * @param int $id Folder id.
253 246 * @param array $next Row after.
254 247 * @param array $prev Row before.
255 248 */
256 - do_action( 'desktop_mode_folder_shared', $folder_id, $next, $prev );
249 + do_action( 'openstation_folder_shared', $folder_id, $next, $prev );
257 250 }
258 251
259 252 return true;
260 253 }
@@ -282,23 +275,21 @@
282 275 * 4. Every placement INSIDE the folder (parent_id=$folder_id) is
283 276 * deleted with tombstones.
284 277 * 5. The folder row itself is deleted with a folder tombstone.
285 278 *
286 - * @since 0.9.0
287 - *
288 279 * @param int $folder_id Folder id.
289 280 * @param int $user_id Acting user.
290 281 * @return true|WP_Error
291 282 */
292 -function desktop_mode_files_delete_folder( $folder_id, $user_id ) {
283 +function openstation_files_delete_folder( $folder_id, $user_id ) {
293 284 $folder_id = (int) $folder_id;
294 285 $user_id = (int) $user_id;
295 - $row = desktop_mode_files_get_folder( $folder_id );
286 + $row = openstation_files_get_folder( $folder_id );
296 287 if ( ! $row ) {
297 - return new WP_Error( 'desktop_mode_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
288 + return new WP_Error( 'openstation_files_not_found', __( 'Folder not found.', 'desktop-mode' ), array( 'status' => 404 ) );
298 289 }
299 290 if ( (int) $row['owner_id'] !== $user_id ) {
300 - return new WP_Error( 'desktop_mode_files_forbidden', __( 'You cannot delete this folder.', 'desktop-mode' ), array( 'status' => 403 ) );
291 + return new WP_Error( 'openstation_files_forbidden', __( 'You cannot delete this folder.', 'desktop-mode' ), array( 'status' => 403 ) );
301 292 }
302 293
303 294 /**
304 295 * Filter whether a folder delete is allowed to proceed. Default
@@ -310,10 +301,8 @@
310 301 * guard for accidental cascades).
311 302 * - Require a confirmation token / nonce stored in the user's
312 303 * session.
313 304 *
314 - * @since 0.8.5
315 - *
316 305 * @param bool|WP_Error $can Default `true`.
317 306 * @param int $folder_id Folder id about to be deleted.
318 307 * @param int $user_id Acting user (folder owner).
319 308 * @param array $row Folder row.
@@ -318,9 +307,9 @@
318 307 * @param int $user_id Acting user (folder owner).
319 308 * @param array $row Folder row.
320 309 */
321 310 $can = apply_filters(
322 - 'desktop_mode_files_can_delete_folder',
311 + 'openstation_files_can_delete_folder',
323 312 true,
324 313 $folder_id,
325 314 $user_id,
326 315 $row
@@ -329,9 +318,9 @@
329 318 return $can;
330 319 }
331 320 if ( true !== $can ) {
332 321 return new WP_Error(
333 - 'desktop_mode_files_delete_vetoed',
322 + 'openstation_files_delete_vetoed',
334 323 __( 'A plugin blocked this folder from being deleted.', 'desktop-mode' ),
335 324 array( 'status' => 403 )
336 325 );
337 326 }
@@ -341,24 +330,22 @@
341 330 * Listeners can persist a snapshot, log an audit entry, or
342 331 * stage a notification to recipients ("the folder you had
343 332 * access to is being deleted in 10 s").
344 333 *
345 - * @since 0.8.5
346 - *
347 334 * @param int $folder_id Folder being deleted.
348 335 * @param int $user_id Acting user.
349 336 * @param array $row Folder row.
350 337 */
351 - do_action( 'desktop_mode_files_before_delete_folder', $folder_id, $user_id, $row );
338 + do_action( 'openstation_files_before_delete_folder', $folder_id, $user_id, $row );
352 339
353 340 $visited = array();
354 341 $summary = array(
355 - 'folders_deleted' => array(),
356 - 'shares_revoked' => array(),
357 - 'placements_pointing' => array(),
358 - 'placements_inside' => array(),
342 + 'folders_deleted' => array(),
343 + 'shares_revoked' => array(),
344 + 'placements_pointing' => array(),
345 + 'placements_inside' => array(),
359 346 );
360 - $result = desktop_mode_files_delete_folder_recursive( $folder_id, $user_id, $visited, $summary );
347 + $result = openstation_files_delete_folder_recursive( $folder_id, $user_id, $visited, $summary );
361 348 if ( is_wp_error( $result ) ) {
362 349 return $result;
363 350 }
364 351
@@ -375,16 +362,14 @@
375 362 * any deleted folder, across users).
376 363 * - `placements_inside` — placement ids removed (contents of
377 364 * the deleted folders).
378 365 *
379 - * @since 0.8.5
380 - *
381 366 * @param int $folder_id Root folder of the cascade.
382 367 * @param int $user_id Acting user.
383 368 * @param array $summary Cascade summary (see above).
384 369 */
385 370 do_action(
386 - 'desktop_mode_files_after_delete_folder_cascade',
371 + 'openstation_files_after_delete_folder_cascade',
387 372 $folder_id,
388 373 $user_id,
389 374 $summary
390 375 );
@@ -392,9 +377,9 @@
392 377 return true;
393 378 }
394 379
395 380 /**
396 - * Recursive worker for {@see desktop_mode_files_delete_folder}.
381 + * Recursive worker for {@see openstation_files_delete_folder}.
397 382 *
398 383 * Walks the folder's sub-tree (sub-folders the same owner owns),
399 384 * then on the way back up cleans up share rows, decisions,
400 385 * pointing-at placements, contained placements, and the folder
@@ -404,9 +389,8 @@
404 389 * `$visited` guards against cycles in case the placement graph is
405 390 * ever corrupted with one. The owner check happens at the public
406 391 * entry point above; this worker trusts its caller.
407 392 *
408 - * @since 0.8.5
409 393 * @internal
410 394 *
411 395 * @param int $folder_id Folder id to delete.
412 396 * @param int $user_id Owner.
@@ -417,9 +401,9 @@
417 401 * `placements_inside`); initialized when
418 402 * null.
419 403 * @return true|WP_Error
420 404 */
421 -function desktop_mode_files_delete_folder_recursive( $folder_id, $user_id, &$visited, &$summary = null ) {
405 +function openstation_files_delete_folder_recursive( $folder_id, $user_id, &$visited, &$summary = null ) {
422 406 global $wpdb;
423 407 $folder_id = (int) $folder_id;
424 408 if ( isset( $visited[ $folder_id ] ) ) {
425 409 return true;
@@ -424,29 +408,29 @@
424 408 if ( isset( $visited[ $folder_id ] ) ) {
425 409 return true;
426 410 }
427 411 $visited[ $folder_id ] = true;
428 - $row = desktop_mode_files_get_folder( $folder_id );
412 + $row = openstation_files_get_folder( $folder_id );
429 413 if ( ! $row ) {
430 414 return true;
431 415 }
432 416
433 - $tables = desktop_mode_files_table_names();
417 + $tables = openstation_files_table_names();
434 418 if ( null === $summary || ! is_array( $summary ) ) {
435 419 $summary = array(
436 - 'folders_deleted' => array(),
437 - 'shares_revoked' => array(),
438 - 'placements_pointing' => array(),
439 - 'placements_inside' => array(),
420 + 'folders_deleted' => array(),
421 + 'shares_revoked' => array(),
422 + 'placements_pointing' => array(),
423 + 'placements_inside' => array(),
440 424 );
441 425 }
442 426
443 427 // 1) Recurse into sub-folders the owner owns. A sub-folder
444 - // owned by SOMEONE ELSE (e.g. a writer recipient who built
445 - // their own folder inside this one) is left intact —
446 - // deleting the parent only severs the containment for the
447 - // owner; the sub-folder's owner can still reach it through
448 - // their own placements.
428 + // owned by SOMEONE ELSE (e.g. a writer recipient who built
429 + // their own folder inside this one) is left intact —
430 + // deleting the parent only severs the containment for the
431 + // owner; the sub-folder's owner can still reach it through
432 + // their own placements.
449 433 $sub_folder_refs = (array) $wpdb->get_col(
450 434 $wpdb->prepare(
451 435 "SELECT DISTINCT file_ref FROM {$tables['placements']}
452 436 WHERE parent_id = %d
@@ -458,26 +442,30 @@
458 442 $sub_id = (int) $ref;
459 443 if ( $sub_id <= 0 || $sub_id === $folder_id ) {
460 444 continue;
461 445 }
462 - $sub_row = desktop_mode_files_get_folder( $sub_id );
446 + $sub_row = openstation_files_get_folder( $sub_id );
463 447 if ( $sub_row && (int) $sub_row['owner_id'] === $user_id ) {
464 - desktop_mode_files_delete_folder_recursive( $sub_id, $user_id, $visited, $summary );
448 + openstation_files_delete_folder_recursive( $sub_id, $user_id, $visited, $summary );
465 449 }
466 450 }
467 451
468 452 // 2) Revoke every share for this folder: shares table + per-
469 - // user decisions table. The folder is going away, so the
470 - // rows are obsolete; leaving them would let the heartbeat
471 - // keep delivering a `removed` tombstone for ghost rows.
453 + // user decisions table. The folder is going away, so the
454 + // rows are obsolete; leaving them would let the heartbeat
455 + // keep delivering a `removed` tombstone for ghost rows.
456 + // `target_type` scoping is load-bearing: `folder_id` carries a
457 + // STORED-FILE id on `target_type='file'` rows — without the
458 + // predicate this cascade would revoke an unrelated user's file
459 + // share whose id collides with the deleted folder's.
472 460 $share_rows = (array) $wpdb->get_results(
473 461 $wpdb->prepare(
474 - "SELECT * FROM {$tables['shares']} WHERE folder_id = %d",
462 + "SELECT * FROM {$tables['shares']} WHERE target_type = 'folder' AND folder_id = %d",
475 463 $folder_id
476 464 ),
477 465 ARRAY_A
478 466 );
479 - $share_ids = array();
467 + $share_ids = array();
480 468 foreach ( $share_rows as $share_row ) {
481 469 $share_ids[] = (int) $share_row['id'];
482 470 }
483 471 if ( ! empty( $share_ids ) ) {
@@ -501,11 +489,11 @@
501 489 // don't have to also subscribe to the cascade-specific
502 490 // hook. `$row` carries the pre-delete share data so
503 491 // listeners can read principal / capability for audit.
504 492 foreach ( $share_rows as $share_row ) {
505 - /** @see desktop_mode_folder_share_revoke */
493 + /** @see openstation_folder_share_revoke */
506 494 do_action(
507 - 'desktop_mode_files_share_revoked',
495 + 'openstation_files_share_revoked',
508 496 (int) $share_row['id'],
509 497 $share_row,
510 498 $user_id
511 499 );
@@ -516,9 +504,9 @@
516 504 );
517 505 }
518 506
519 507 // 3) Placements POINTING AT this folder (every recipient's
520 - // accept-created root placement, plus the owner's own).
508 + // accept-created root placement, plus the owner's own).
521 509 $pointing_ids = (array) $wpdb->get_col(
522 510 $wpdb->prepare(
523 511 "SELECT id FROM {$tables['placements']}
524 512 WHERE file_type = 'folder' AND file_ref = %s",
@@ -525,9 +513,9 @@
525 513 (string) $folder_id
526 514 )
527 515 );
528 516 foreach ( $pointing_ids as $pid ) {
529 - desktop_mode_files_write_tombstone( 'placement', (int) $pid );
517 + openstation_files_write_tombstone( 'placement', (int) $pid );
530 518 }
531 519 if ( ! empty( $pointing_ids ) ) {
532 520 $placeholders = implode( ',', array_fill( 0, count( $pointing_ids ), '%d' ) );
533 521 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
@@ -543,24 +531,41 @@
543 531 );
544 532 }
545 533
546 534 // 4) Placements INSIDE this folder. After step 1 the sub-folder
547 - // placements have been recursively handled for owner-owned
548 - // sub-folders; whatever remains here (loose post / link /
549 - // user / etc. placements, plus orphan folder placements
550 - // whose folder we did NOT recurse into because someone else
551 - // owns it) gets deleted with a tombstone each.
552 - $inside_ids = (array) $wpdb->get_col(
535 + // placements have been recursively handled for owner-owned
536 + // sub-folders; whatever remains here (loose post / link /
537 + // user / etc. placements, plus orphan folder placements
538 + // whose folder we did NOT recurse into because someone else
539 + // owns it) gets deleted with a tombstone each.
540 + $inside_rows = (array) $wpdb->get_results(
553 541 $wpdb->prepare(
554 - "SELECT id FROM {$tables['placements']} WHERE parent_id = %d",
542 + "SELECT * FROM {$tables['placements']} WHERE parent_id = %d",
555 543 $folder_id
556 - )
544 + ),
545 + ARRAY_A
557 546 );
558 - foreach ( $inside_ids as $cid ) {
559 - desktop_mode_files_write_tombstone( 'placement', (int) $cid );
547 + $inside_ids = array();
548 + foreach ( $inside_rows as $inside_row ) {
549 + $inside_ids[] = (int) $inside_row['id'];
550 + openstation_files_write_tombstone( 'placement', (int) $inside_row['id'] );
560 551 }
561 552 if ( ! empty( $inside_ids ) ) {
562 553 $wpdb->delete( $tables['placements'], array( 'parent_id' => $folder_id ), array( '%d' ) );
554 + // Upload placements carry real bytes — run the stored-files
555 + // deletion contract now that the rows are gone. Direct
556 + // guarded call (not the public unplaced action) so cascade
557 + // hook semantics for other types stay unchanged.
558 + if ( function_exists( 'openstation_stored_files_handle_unplaced' ) ) {
559 + foreach ( $inside_rows as $inside_row ) {
560 + if ( 'upload' === (string) $inside_row['file_type'] ) {
561 + openstation_stored_files_handle_unplaced(
562 + (int) $inside_row['id'],
563 + openstation_files_normalize_placement_row( $inside_row )
564 + );
565 + }
566 + }
567 + }
563 568 $summary['placements_inside'] = array_merge(
564 569 $summary['placements_inside'],
565 570 array_map( 'intval', $inside_ids )
566 571 );
@@ -568,25 +573,23 @@
568 573
569 574 // 5) The folder row itself + its tombstone.
570 575 $ok = $wpdb->delete( $tables['folders'], array( 'id' => $folder_id ), array( '%d' ) );
571 576 if ( false === $ok ) {
572 - return new WP_Error( 'desktop_mode_files_delete_failed', __( 'Failed to delete folder.', 'desktop-mode' ), array( 'status' => 500 ) );
577 + return new WP_Error( 'openstation_files_delete_failed', __( 'Failed to delete folder.', 'desktop-mode' ), array( 'status' => 500 ) );
573 578 }
574 - desktop_mode_files_write_tombstone( 'folder', $folder_id );
579 + openstation_files_write_tombstone( 'folder', $folder_id );
575 580 $summary['folders_deleted'][] = $folder_id;
576 581
577 582 /**
578 583 * Fires after a folder is deleted. Plugins listening for share
579 584 * lifecycle can subscribe alongside
580 - * `desktop_mode_files_share_revoked` if they want to react to
585 + * `openstation_files_share_revoked` if they want to react to
581 586 * cascade-revokes triggered by folder deletion.
582 587 *
583 - * @since 0.9.0
584 - *
585 588 * @param int $id Folder id.
586 589 * @param array $row Removed row.
587 590 */
588 - do_action( 'desktop_mode_folder_deleted', $folder_id, $row );
591 + do_action( 'openstation_folder_deleted', $folder_id, $row );
589 592
590 593 return true;
591 594 }
592 595
@@ -592,10 +595,8 @@
592 595
593 596 /**
594 597 * Lookup a folder row by id.
595 598 *
596 - * @since 0.9.0
597 - *
598 599 * @param int $folder_id Folder id.
599 600 * @param bool $include_trashed Optional. Return the row even when
600 601 * soft-trashed (recycle-bin callers).
601 602 * Default false — trashed folders
@@ -601,11 +602,11 @@
601 602 * Default false — trashed folders
602 603 * resolve to null.
603 604 * @return array|null
604 605 */
605 -function desktop_mode_files_get_folder( $folder_id, $include_trashed = false ) {
606 +function openstation_files_get_folder( $folder_id, $include_trashed = false ) {
606 607 global $wpdb;
607 - $tables = desktop_mode_files_table_names();
608 + $tables = openstation_files_table_names();
608 609 $row = $wpdb->get_row(
609 610 $wpdb->prepare( "SELECT * FROM {$tables['folders']} WHERE id = %d", (int) $folder_id ),
610 611 ARRAY_A
611 612 );
@@ -616,22 +617,20 @@
616 617 // default — recycle-bin callers pass `true` to opt in.
617 618 if ( ! $include_trashed && ! empty( $row['trashed_at_ms'] ) ) {
618 619 return null;
619 620 }
620 - return desktop_mode_files_normalize_folder_row( $row );
621 + return openstation_files_normalize_folder_row( $row );
621 622 }
622 623
623 624 /**
624 625 * Folders visible to `$user_id`. Returns the folders the viewer
625 626 * owns; sharing.php merges shared folders in via the
626 - * `desktop_mode_files_visible_folders` filter.
627 + * `openstation_files_visible_folders` filter.
627 628 *
628 - * @since 0.9.0
629 - *
630 629 * @param int $user_id Viewer.
631 630 * @return array[]
632 631 */
633 -function desktop_mode_files_get_visible_folders( $user_id ) {
632 +function openstation_files_get_visible_folders( $user_id ) {
634 633 global $wpdb;
635 634 $user_id = (int) $user_id;
636 635 if ( $user_id <= 0 ) {
637 636 return array();
@@ -636,9 +635,9 @@
636 635 if ( $user_id <= 0 ) {
637 636 return array();
638 637 }
639 638
640 - $tables = desktop_mode_files_table_names();
639 + $tables = openstation_files_table_names();
641 640 $rows = $wpdb->get_results(
642 641 $wpdb->prepare(
643 642 "SELECT * FROM {$tables['folders']}
644 643 WHERE owner_id = %d AND trashed_at_ms IS NULL",
@@ -645,35 +644,32 @@
645 644 $user_id
646 645 ),
647 646 ARRAY_A
648 647 );
649 - $out = array();
648 + $out = array();
650 649 foreach ( (array) $rows as $row ) {
651 - $out[] = desktop_mode_files_normalize_folder_row( $row );
650 + $out[] = openstation_files_normalize_folder_row( $row );
652 651 }
653 652
654 653 /**
655 654 * Filter the folders visible to a viewer. sharing.php's
656 - * `desktop_mode_files_compute_visible_folders` (priority 5)
655 + * `openstation_files_compute_visible_folders` (priority 5)
657 656 * merges accepted shares and `share_mode='all'` folders onto
658 657 * this list.
659 658 *
660 - * @since 0.9.0
661 - *
662 659 * @param array[] $folders Folders the viewer owns.
663 660 * @param int $user_id Viewer.
664 661 */
665 - return (array) apply_filters( 'desktop_mode_files_visible_folders', $out, $user_id );
662 + return (array) apply_filters( 'openstation_files_visible_folders', $out, $user_id );
666 663 }
667 664
668 665 /**
669 - * @since 0.9.0
670 666 * @internal
671 667 *
672 668 * @param array $row Raw wpdb row.
673 669 * @return array
674 670 */
675 -function desktop_mode_files_normalize_folder_row( $row ) {
671 +function openstation_files_normalize_folder_row( $row ) {
676 672 $meta_raw = isset( $row['share_meta'] ) ? (string) $row['share_meta'] : '';
677 673 $meta = '' !== $meta_raw ? json_decode( $meta_raw, true ) : null;
678 674 return array(
679 675 'id' => (int) $row['id'],