| @@ -1,10 +1,10 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Desktop Mode — Files placement store. | |
| 3 | + * OpenStation — Files placement store. | |
| 4 | 4 | * |
| 5 | 5 | * CRUD primitives for the `_desktop_mode_file_placements` table. |
| 6 | - * Every read goes through `desktop_mode_files_query_args` so | |
| 6 | + * Every read goes through `openstation_files_query_args` so | |
| 7 | 7 | * plugins can scope what's visible (mirror of the recycle-bin's |
| 8 | 8 | * filter pattern). Every write fires before / after actions so |
| 9 | 9 | * other plugins can react and so Phase 6's Heartbeat sync has a |
| 10 | 10 | * single subscription point. |
| @@ -10,17 +10,17 @@ | ||
| 10 | 10 | * single subscription point. |
| 11 | 11 | * |
| 12 | 12 | * Capability gate is per-call: callers pass the `$user_id` they |
| 13 | 13 | * intend to act for; the function consults |
| 14 | - * `desktop_mode_files_can_place` (filter) and the file's | |
| 15 | - * `Desktop_Mode_File::can_read()` before writing. | |
| 14 | + * `openstation_files_can_place` (filter) and the file's | |
| 15 | + * `OpenStation_File::can_read()` before writing. | |
| 16 | 16 | * |
| 17 | 17 | * Tombstones are written only for permanent removals (hard |
| 18 | 18 | * deletes); soft-trash and moves are surfaced to clients via |
| 19 | 19 | * `updated_at_ms` / `trashed_at_ms` in the Heartbeat delta — see |
| 20 | - * desktop_mode_files_write_tombstone() for the invariant. | |
| 20 | + * openstation_files_write_tombstone() for the invariant. | |
| 21 | 21 | * |
| 22 | - * @package WPDesktopMode | |
| 22 | + * @package OpenStation | |
| 23 | 23 | */ |
| 24 | 24 | |
| 25 | 25 | defined( 'ABSPATH' ) || exit; |
| 26 | 26 | |
| @@ -34,9 +34,9 @@ | ||
| 34 | 34 | * @param string $ref Entity reference. |
| 35 | 35 | * @param array $args Optional. `x`, `y`, `sort_order`, `meta`. |
| 36 | 36 | * @return int|WP_Error Placement id on success, `WP_Error` otherwise. |
| 37 | 37 | */ |
| 38 | -function desktop_mode_files_place( $user_id, $parent_id, $type, $ref, $args = array() ) { | |
| 38 | +function openstation_files_place( $user_id, $parent_id, $type, $ref, $args = array() ) { | |
| 39 | 39 | global $wpdb; |
| 40 | 40 | |
| 41 | 41 | $user_id = (int) $user_id; |
| 42 | 42 | $parent_id = (int) $parent_id; |
| @@ -43,13 +43,13 @@ | ||
| 43 | 43 | $type = (string) $type; |
| 44 | 44 | $ref = (string) $ref; |
| 45 | 45 | |
| 46 | 46 | if ( $user_id <= 0 ) { |
| 47 | - return new WP_Error( 'desktop_mode_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) ); | |
| 47 | + return new WP_Error( 'openstation_files_invalid_user', __( 'A user id is required.', 'desktop-mode' ), array( 'status' => 400 ) ); | |
| 48 | 48 | } |
| 49 | - $entry = desktop_mode_get_file_type( $type ); | |
| 49 | + $entry = openstation_get_file_type( $type ); | |
| 50 | 50 | if ( ! $entry ) { |
| 51 | - return new WP_Error( 'desktop_mode_files_unknown_type', __( 'Unknown file type.', 'desktop-mode' ), array( 'status' => 400 ) ); | |
| 51 | + return new WP_Error( 'openstation_files_unknown_type', __( 'Unknown file type.', 'desktop-mode' ), array( 'status' => 400 ) ); | |
| 52 | 52 | } |
| 53 | 53 | |
| 54 | 54 | /** |
| 55 | 55 | * Gate placement creation. Defaults to allowing the user to |
| @@ -60,13 +60,13 @@ | ||
| 60 | 60 | * @param int $user_id Owner. |
| 61 | 61 | * @param string $type File-type slug. |
| 62 | 62 | * @param string $ref Entity reference. |
| 63 | 63 | */ |
| 64 | - $file = desktop_mode_resolve_file( $type, $ref ); | |
| 64 | + $file = openstation_resolve_file( $type, $ref ); | |
| 65 | 65 | $can = $file ? $file->can_read( $user_id ) : false; |
| 66 | - $can = (bool) apply_filters( 'desktop_mode_files_can_place', $can, $user_id, $type, $ref ); | |
| 66 | + $can = (bool) apply_filters( 'openstation_files_can_place', $can, $user_id, $type, $ref ); | |
| 67 | 67 | if ( ! $can ) { |
| 68 | - return new WP_Error( 'desktop_mode_files_forbidden', __( 'You are not allowed to place this file.', 'desktop-mode' ), array( 'status' => 403 ) ); | |
| 68 | + return new WP_Error( 'openstation_files_forbidden', __( 'You are not allowed to place this file.', 'desktop-mode' ), array( 'status' => 403 ) ); | |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | 71 | // Write-gate: placing INTO a non-owned folder requires the |
| 72 | 72 | // folder's `write` cap. Owner / desktop-root placements are |
| @@ -71,16 +71,16 @@ | ||
| 71 | 71 | // Write-gate: placing INTO a non-owned folder requires the |
| 72 | 72 | // folder's `write` cap. Owner / desktop-root placements are |
| 73 | 73 | // always allowed. |
| 74 | 74 | if ( (int) $parent_id > 0 ) { |
| 75 | - $target_folder = desktop_mode_files_get_folder( (int) $parent_id ); | |
| 75 | + $target_folder = openstation_files_get_folder( (int) $parent_id ); | |
| 76 | 76 | if ( $target_folder && (int) $target_folder['owner_id'] !== $user_id ) { |
| 77 | - $cap = function_exists( 'desktop_mode_folder_share_user_capability' ) | |
| 78 | - ? desktop_mode_folder_share_user_capability( (int) $parent_id, $user_id ) | |
| 77 | + $cap = function_exists( 'openstation_folder_share_user_capability' ) | |
| 78 | + ? openstation_folder_share_user_capability( (int) $parent_id, $user_id ) | |
| 79 | 79 | : 'none'; |
| 80 | 80 | if ( 'write' !== $cap ) { |
| 81 | 81 | return new WP_Error( |
| 82 | - 'desktop_mode_files_no_write_in_shared_folder', | |
| 82 | + 'openstation_files_no_write_in_shared_folder', | |
| 83 | 83 | __( 'You only have read access to that folder.', 'desktop-mode' ), |
| 84 | 84 | array( 'status' => 403 ) |
| 85 | 85 | ); |
| 86 | 86 | } |
| @@ -96,10 +96,10 @@ | ||
| 96 | 96 | 'meta' => null, |
| 97 | 97 | ) |
| 98 | 98 | ); |
| 99 | 99 | |
| 100 | - $tables = desktop_mode_files_table_names(); | |
| 101 | - $now = desktop_mode_files_now_ms(); | |
| 100 | + $tables = openstation_files_table_names(); | |
| 101 | + $now = openstation_files_now_ms(); | |
| 102 | 102 | $row = array( |
| 103 | 103 | 'owner_id' => $user_id, |
| 104 | 104 | 'updated_by' => $user_id, |
| 105 | 105 | 'parent_id' => max( 0, $parent_id ), |
| @@ -118,21 +118,30 @@ | ||
| 118 | 118 | // `<div class="wpdberror">…</div>` to the REST response body and |
| 119 | 119 | // break `await response.json()` on the client. `$wpdb->last_error` |
| 120 | 120 | // still holds the message, so genuine DB failures surface via the |
| 121 | 121 | // `WP_Error` we return when no existing row is found. |
| 122 | - $prev_suppress = $wpdb->suppress_errors( true ); | |
| 123 | - $ok = $wpdb->insert( $tables['placements'], $row, array( '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%d', '%d', '%s' ) ); | |
| 124 | - $wpdb->suppress_errors( $prev_suppress ); | |
| 122 | + $insert = static function () use ( $tables, $row ) { | |
| 123 | + global $wpdb; | |
| 124 | + $prev_suppress = $wpdb->suppress_errors( true ); | |
| 125 | + $ok = $wpdb->insert( $tables['placements'], $row, array( '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%d', '%d', '%s' ) ); | |
| 126 | + $wpdb->suppress_errors( $prev_suppress ); | |
| 127 | + return array( $ok, (int) $wpdb->insert_id ); | |
| 128 | + }; | |
| 129 | + $result = 'upload' === $type ? openstation_stored_files_place_insert( $ref, $insert ) : $insert(); | |
| 130 | + if ( is_wp_error( $result ) ) { | |
| 131 | + return $result; | |
| 132 | + } | |
| 133 | + list( $ok, $id ) = $result; | |
| 125 | 134 | if ( false === $ok ) { |
| 126 | 135 | // Disambiguate the two cases hidden behind a generic `false`: |
| 127 | - // (a) The `placement_unique` index collided | |
| 128 | - // with an existing row for this (user, parent, type, | |
| 129 | - // ref). The collider may be active (the orphan placer | |
| 130 | - // won a race against this caller, or a stale duplicate | |
| 131 | - // client request) or soft-trashed (the user removed a | |
| 132 | - // link tile and is now recreating the same URL). | |
| 133 | - // (b) Any other DB failure — connection, deadlock, bad | |
| 134 | - // column. The error must surface to the caller as-is. | |
| 136 | + // (a) The `placement_unique` index collided | |
| 137 | + // with an existing row for this (user, parent, type, | |
| 138 | + // ref). The collider may be active (the orphan placer | |
| 139 | + // won a race against this caller, or a stale duplicate | |
| 140 | + // client request) or soft-trashed (the user removed a | |
| 141 | + // link tile and is now recreating the same URL). | |
| 142 | + // (b) Any other DB failure — connection, deadlock, bad | |
| 143 | + // column. The error must surface to the caller as-is. | |
| 135 | 144 | // We treat (a) idempotently: restore if trashed, then apply |
| 136 | 145 | // the caller's coords / meta so the new placement lands |
| 137 | 146 | // where the user clicked. Reported as #167. |
| 138 | 147 | $existing = $wpdb->get_row( |
| @@ -150,15 +159,15 @@ | ||
| 150 | 159 | ), |
| 151 | 160 | ARRAY_A |
| 152 | 161 | ); |
| 153 | 162 | if ( ! $existing ) { |
| 154 | - return new WP_Error( 'desktop_mode_files_insert_failed', __( 'Failed to write placement.', 'desktop-mode' ), array( 'status' => 500 ) ); | |
| 163 | + return new WP_Error( 'openstation_files_insert_failed', __( 'Failed to write placement.', 'desktop-mode' ), array( 'status' => 500 ) ); | |
| 155 | 164 | } |
| 156 | 165 | |
| 157 | 166 | $existing_id = (int) $existing['id']; |
| 158 | 167 | |
| 159 | 168 | if ( ! empty( $existing['trashed_at_ms'] ) ) { |
| 160 | - $restore = desktop_mode_files_restore_placement( $user_id, $existing_id ); | |
| 169 | + $restore = openstation_files_restore_placement( $user_id, $existing_id ); | |
| 161 | 170 | if ( is_wp_error( $restore ) ) { |
| 162 | 171 | return $restore; |
| 163 | 172 | } |
| 164 | 173 | } |
| @@ -168,11 +177,11 @@ | ||
| 168 | 177 | // stale tombstones for this id should be cleared so a fresh |
| 169 | 178 | // heartbeat tick can't surface "alive + removed" together. |
| 170 | 179 | // `restore_placement` already clears its own tombstones, so |
| 171 | 180 | // this is a no-op in the soft-trashed branch above. |
| 172 | - desktop_mode_files_clear_tombstones_for( 'placement', $existing_id ); | |
| 181 | + openstation_files_clear_tombstones_for( 'placement', $existing_id ); | |
| 173 | 182 | |
| 174 | - $move = desktop_mode_files_move( | |
| 183 | + $move = openstation_files_move( | |
| 175 | 184 | $existing_id, |
| 176 | 185 | $user_id, |
| 177 | 186 | array( |
| 178 | 187 | 'parent_id' => max( 0, $parent_id ), |
| @@ -187,10 +196,8 @@ | ||
| 187 | 196 | } |
| 188 | 197 | |
| 189 | 198 | return $existing_id; |
| 190 | 199 | } |
| 191 | - $id = (int) $wpdb->insert_id; | |
| 192 | - | |
| 193 | 200 | $row['id'] = $id; |
| 194 | 201 | |
| 195 | 202 | /** |
| 196 | 203 | * Fires after a placement is created. |
| @@ -197,9 +204,9 @@ | ||
| 197 | 204 | * |
| 198 | 205 | * @param int $id Placement id. |
| 199 | 206 | * @param array $row Inserted row. |
| 200 | 207 | */ |
| 201 | - do_action( 'desktop_mode_file_placed', $id, $row ); | |
| 208 | + do_action( 'openstation_file_placed', $id, $row ); | |
| 202 | 209 | |
| 203 | 210 | return $id; |
| 204 | 211 | } |
| 205 | 212 | |
| @@ -214,20 +221,20 @@ | ||
| 214 | 221 | * @param int $user_id Acting user (for capability gate). |
| 215 | 222 | * @param array $changes `parent_id`, `x`, `y`, `sort_order`, `meta`. |
| 216 | 223 | * @return true|WP_Error |
| 217 | 224 | */ |
| 218 | -function desktop_mode_files_move( $placement_id, $user_id, $changes = array() ) { | |
| 225 | +function openstation_files_move( $placement_id, $user_id, $changes = array() ) { | |
| 219 | 226 | global $wpdb; |
| 220 | 227 | |
| 221 | 228 | $placement_id = (int) $placement_id; |
| 222 | 229 | $user_id = (int) $user_id; |
| 223 | 230 | if ( $placement_id <= 0 || $user_id <= 0 ) { |
| 224 | - return new WP_Error( 'desktop_mode_files_bad_request', __( 'Invalid arguments.', 'desktop-mode' ), array( 'status' => 400 ) ); | |
| 231 | + return new WP_Error( 'openstation_files_bad_request', __( 'Invalid arguments.', 'desktop-mode' ), array( 'status' => 400 ) ); | |
| 225 | 232 | } |
| 226 | 233 | |
| 227 | - $row = desktop_mode_files_get_placement( $placement_id ); | |
| 234 | + $row = openstation_files_get_placement( $placement_id ); | |
| 228 | 235 | if ( ! $row ) { |
| 229 | - return new WP_Error( 'desktop_mode_files_not_found', __( 'Placement not found.', 'desktop-mode' ), array( 'status' => 404 ) ); | |
| 236 | + return new WP_Error( 'openstation_files_not_found', __( 'Placement not found.', 'desktop-mode' ), array( 'status' => 404 ) ); | |
| 230 | 237 | } |
| 231 | 238 | |
| 232 | 239 | // Owner-lock for `upload` placements: only the stored file's |
| 233 | 240 | // owner may move them — folder-share write capability does NOT |
| @@ -232,9 +239,9 @@ | ||
| 232 | 239 | // Owner-lock for `upload` placements: only the stored file's |
| 233 | 240 | // owner may move them — folder-share write capability does NOT |
| 234 | 241 | // extend to uploaded files (recipients are read + download |
| 235 | 242 | // only; see stored-files-store.php). |
| 236 | - $upload_lock = desktop_mode_files_upload_owner_lock( $row, $user_id ); | |
| 243 | + $upload_lock = openstation_files_upload_owner_lock( $row, $user_id ); | |
| 237 | 244 | if ( is_wp_error( $upload_lock ) ) { |
| 238 | 245 | return $upload_lock; |
| 239 | 246 | } |
| 240 | 247 | |
| @@ -244,17 +251,17 @@ | ||
| 244 | 251 | // every icon in it, regardless of which user originally placed |
| 245 | 252 | // the row (shared-namespace semantics). |
| 246 | 253 | $is_row_owner = (int) $row['owner_id'] === $user_id; |
| 247 | 254 | if ( (int) $row['parent_id'] > 0 ) { |
| 248 | - $source_folder = desktop_mode_files_get_folder( (int) $row['parent_id'] ); | |
| 255 | + $source_folder = openstation_files_get_folder( (int) $row['parent_id'] ); | |
| 249 | 256 | if ( $source_folder ) { |
| 250 | 257 | $is_folder_owner = (int) $source_folder['owner_id'] === $user_id; |
| 251 | - $source_cap = function_exists( 'desktop_mode_folder_share_user_capability' ) | |
| 252 | - ? desktop_mode_folder_share_user_capability( (int) $row['parent_id'], $user_id ) | |
| 258 | + $source_cap = function_exists( 'openstation_folder_share_user_capability' ) | |
| 259 | + ? openstation_folder_share_user_capability( (int) $row['parent_id'], $user_id ) | |
| 253 | 260 | : 'none'; |
| 254 | 261 | if ( ! $is_row_owner && ! $is_folder_owner && 'write' !== $source_cap ) { |
| 255 | 262 | return new WP_Error( |
| 256 | - 'desktop_mode_files_no_write_in_shared_folder', | |
| 263 | + 'openstation_files_no_write_in_shared_folder', | |
| 257 | 264 | __( 'You only have read access to this folder.', 'desktop-mode' ), |
| 258 | 265 | array( 'status' => 403 ) |
| 259 | 266 | ); |
| 260 | 267 | } |
| @@ -260,9 +267,9 @@ | ||
| 260 | 267 | } |
| 261 | 268 | // Folder reader on their own row inside the folder — still no. |
| 262 | 269 | if ( $is_row_owner && ! $is_folder_owner && 'write' !== $source_cap ) { |
| 263 | 270 | return new WP_Error( |
| 264 | - 'desktop_mode_files_no_write_in_shared_folder', | |
| 271 | + 'openstation_files_no_write_in_shared_folder', | |
| 265 | 272 | __( 'You only have read access to this folder.', 'desktop-mode' ), |
| 266 | 273 | array( 'status' => 403 ) |
| 267 | 274 | ); |
| 268 | 275 | } |
| @@ -268,21 +275,21 @@ | ||
| 268 | 275 | } |
| 269 | 276 | } |
| 270 | 277 | } elseif ( ! $is_row_owner ) { |
| 271 | 278 | // Row at root, viewer doesn't own it. |
| 272 | - return new WP_Error( 'desktop_mode_files_forbidden', __( 'You cannot edit this placement.', 'desktop-mode' ), array( 'status' => 403 ) ); | |
| 279 | + return new WP_Error( 'openstation_files_forbidden', __( 'You cannot edit this placement.', 'desktop-mode' ), array( 'status' => 403 ) ); | |
| 273 | 280 | } |
| 274 | 281 | if ( isset( $changes['parent_id'] ) ) { |
| 275 | 282 | $target_parent = max( 0, (int) $changes['parent_id'] ); |
| 276 | 283 | if ( $target_parent > 0 ) { |
| 277 | - $target = desktop_mode_files_get_folder( $target_parent ); | |
| 284 | + $target = openstation_files_get_folder( $target_parent ); | |
| 278 | 285 | if ( $target && (int) $target['owner_id'] !== $user_id ) { |
| 279 | - $cap = function_exists( 'desktop_mode_folder_share_user_capability' ) | |
| 280 | - ? desktop_mode_folder_share_user_capability( $target_parent, $user_id ) | |
| 286 | + $cap = function_exists( 'openstation_folder_share_user_capability' ) | |
| 287 | + ? openstation_folder_share_user_capability( $target_parent, $user_id ) | |
| 281 | 288 | : 'none'; |
| 282 | 289 | if ( 'write' !== $cap ) { |
| 283 | 290 | return new WP_Error( |
| 284 | - 'desktop_mode_files_no_write_in_shared_folder', | |
| 291 | + 'openstation_files_no_write_in_shared_folder', | |
| 285 | 292 | __( 'You only have read access to that folder.', 'desktop-mode' ), |
| 286 | 293 | array( 'status' => 403 ) |
| 287 | 294 | ); |
| 288 | 295 | } |
| @@ -299,9 +306,9 @@ | ||
| 299 | 306 | if ( 'folder' === (string) $row['file_type'] && $target_parent > 0 ) { |
| 300 | 307 | $moving_folder_id = (int) $row['file_ref']; |
| 301 | 308 | if ( $moving_folder_id > 0 ) { |
| 302 | 309 | if ( |
| 303 | - desktop_mode_files_would_create_folder_cycle( | |
| 310 | + openstation_files_would_create_folder_cycle( | |
| 304 | 311 | $user_id, |
| 305 | 312 | $moving_folder_id, |
| 306 | 313 | $target_parent |
| 307 | 314 | ) |
| @@ -306,9 +313,9 @@ | ||
| 306 | 313 | $target_parent |
| 307 | 314 | ) |
| 308 | 315 | ) { |
| 309 | 316 | return new WP_Error( |
| 310 | - 'desktop_mode_files_folder_cycle', | |
| 317 | + 'openstation_files_folder_cycle', | |
| 311 | 318 | __( 'A folder cannot be placed inside itself or one of its descendants.', 'desktop-mode' ), |
| 312 | 319 | array( 'status' => 409 ) |
| 313 | 320 | ); |
| 314 | 321 | } |
| @@ -315,9 +322,9 @@ | ||
| 315 | 322 | } |
| 316 | 323 | } |
| 317 | 324 | } |
| 318 | 325 | |
| 319 | - $tables = desktop_mode_files_table_names(); | |
| 326 | + $tables = openstation_files_table_names(); | |
| 320 | 327 | $set = array(); |
| 321 | 328 | $fmt = array(); |
| 322 | 329 | |
| 323 | 330 | if ( isset( $changes['parent_id'] ) ) { |
| @@ -337,22 +344,22 @@ | ||
| 337 | 344 | if ( empty( $set ) ) { |
| 338 | 345 | return true; // No-op. |
| 339 | 346 | } |
| 340 | 347 | |
| 341 | - $set['updated_at_ms'] = desktop_mode_files_now_ms(); | |
| 348 | + $set['updated_at_ms'] = openstation_files_now_ms(); | |
| 342 | 349 | $fmt[] = '%d'; |
| 343 | 350 | // Track who actually fired this mutation so a future |
| 344 | 351 | // `If-Match` 409 can name the session that won the race, |
| 345 | 352 | // not just whoever happens to own the row. |
| 346 | - $set['updated_by'] = $user_id; | |
| 347 | - $fmt[] = '%d'; | |
| 353 | + $set['updated_by'] = $user_id; | |
| 354 | + $fmt[] = '%d'; | |
| 348 | 355 | |
| 349 | 356 | $ok = $wpdb->update( $tables['placements'], $set, array( 'id' => $placement_id ), $fmt, array( '%d' ) ); |
| 350 | 357 | if ( false === $ok ) { |
| 351 | - return new WP_Error( 'desktop_mode_files_update_failed', __( 'Failed to update placement.', 'desktop-mode' ), array( 'status' => 500 ) ); | |
| 358 | + return new WP_Error( 'openstation_files_update_failed', __( 'Failed to update placement.', 'desktop-mode' ), array( 'status' => 500 ) ); | |
| 352 | 359 | } |
| 353 | 360 | |
| 354 | - $next = desktop_mode_files_get_placement( $placement_id ); | |
| 361 | + $next = openstation_files_get_placement( $placement_id ); | |
| 355 | 362 | |
| 356 | 363 | /** |
| 357 | 364 | * Fires after a placement is moved / mutated. |
| 358 | 365 | * |
| @@ -359,9 +366,9 @@ | ||
| 359 | 366 | * @param int $id Placement id. |
| 360 | 367 | * @param array $next Row after the change. |
| 361 | 368 | * @param array $prev Row before the change. |
| 362 | 369 | */ |
| 363 | - do_action( 'desktop_mode_file_moved', $placement_id, $next, $row ); | |
| 370 | + do_action( 'openstation_file_moved', $placement_id, $next, $row ); | |
| 364 | 371 | |
| 365 | 372 | return true; |
| 366 | 373 | } |
| 367 | 374 | |
| @@ -371,20 +378,20 @@ | ||
| 371 | 378 | * @param int $placement_id Placement id. |
| 372 | 379 | * @param int $user_id Acting user. |
| 373 | 380 | * @return true|WP_Error |
| 374 | 381 | */ |
| 375 | -function desktop_mode_files_remove( $placement_id, $user_id ) { | |
| 382 | +function openstation_files_remove( $placement_id, $user_id ) { | |
| 376 | 383 | global $wpdb; |
| 377 | 384 | |
| 378 | 385 | $placement_id = (int) $placement_id; |
| 379 | 386 | $user_id = (int) $user_id; |
| 380 | - $row = desktop_mode_files_get_placement( $placement_id ); | |
| 387 | + $row = openstation_files_get_placement( $placement_id ); | |
| 381 | 388 | if ( ! $row ) { |
| 382 | - return new WP_Error( 'desktop_mode_files_not_found', __( 'Placement not found.', 'desktop-mode' ), array( 'status' => 404 ) ); | |
| 389 | + return new WP_Error( 'openstation_files_not_found', __( 'Placement not found.', 'desktop-mode' ), array( 'status' => 404 ) ); | |
| 383 | 390 | } |
| 384 | 391 | // Owner-lock for `upload` placements — removal is destructive |
| 385 | 392 | // for real bytes, so only the stored file's owner may do it. |
| 386 | - $upload_lock = desktop_mode_files_upload_owner_lock( $row, $user_id ); | |
| 393 | + $upload_lock = openstation_files_upload_owner_lock( $row, $user_id ); | |
| 387 | 394 | if ( is_wp_error( $upload_lock ) ) { |
| 388 | 395 | return $upload_lock; |
| 389 | 396 | } |
| 390 | 397 | // Same shared-namespace rule as the trash gate: owner of the |
| @@ -391,24 +398,24 @@ | ||
| 391 | 398 | // row OR write cap on the parent folder. |
| 392 | 399 | $is_row_owner = (int) $row['owner_id'] === $user_id; |
| 393 | 400 | $allowed = $is_row_owner; |
| 394 | 401 | if ( ! $allowed && (int) $row['parent_id'] > 0 ) { |
| 395 | - $cap = function_exists( 'desktop_mode_folder_share_user_capability' ) | |
| 396 | - ? desktop_mode_folder_share_user_capability( (int) $row['parent_id'], $user_id ) | |
| 402 | + $cap = function_exists( 'openstation_folder_share_user_capability' ) | |
| 403 | + ? openstation_folder_share_user_capability( (int) $row['parent_id'], $user_id ) | |
| 397 | 404 | : 'none'; |
| 398 | 405 | $allowed = 'write' === $cap; |
| 399 | 406 | } |
| 400 | 407 | if ( ! $allowed ) { |
| 401 | - return new WP_Error( 'desktop_mode_files_forbidden', __( 'You cannot remove this placement.', 'desktop-mode' ), array( 'status' => 403 ) ); | |
| 408 | + return new WP_Error( 'openstation_files_forbidden', __( 'You cannot remove this placement.', 'desktop-mode' ), array( 'status' => 403 ) ); | |
| 402 | 409 | } |
| 403 | 410 | |
| 404 | - $tables = desktop_mode_files_table_names(); | |
| 411 | + $tables = openstation_files_table_names(); | |
| 405 | 412 | $ok = $wpdb->delete( $tables['placements'], array( 'id' => $placement_id ), array( '%d' ) ); |
| 406 | 413 | if ( false === $ok ) { |
| 407 | - return new WP_Error( 'desktop_mode_files_delete_failed', __( 'Failed to remove placement.', 'desktop-mode' ), array( 'status' => 500 ) ); | |
| 414 | + return new WP_Error( 'openstation_files_delete_failed', __( 'Failed to remove placement.', 'desktop-mode' ), array( 'status' => 500 ) ); | |
| 408 | 415 | } |
| 409 | 416 | |
| 410 | - desktop_mode_files_write_tombstone( 'placement', $placement_id ); | |
| 417 | + openstation_files_write_tombstone( 'placement', $placement_id ); | |
| 411 | 418 | |
| 412 | 419 | /** |
| 413 | 420 | * Fires after a placement is removed. |
| 414 | 421 | * |
| @@ -414,9 +421,9 @@ | ||
| 414 | 421 | * |
| 415 | 422 | * @param int $id Placement id. |
| 416 | 423 | * @param array $row Removed row. |
| 417 | 424 | */ |
| 418 | - do_action( 'desktop_mode_file_unplaced', $placement_id, $row ); | |
| 425 | + do_action( 'openstation_file_unplaced', $placement_id, $row ); | |
| 419 | 426 | |
| 420 | 427 | return true; |
| 421 | 428 | } |
| 422 | 429 | |
| @@ -433,16 +440,16 @@ | ||
| 433 | 440 | * @param array $row Placement row. |
| 434 | 441 | * @param int $user_id Acting user. |
| 435 | 442 | * @return true|WP_Error |
| 436 | 443 | */ |
| 437 | -function desktop_mode_files_upload_owner_lock( $row, $user_id ) { | |
| 444 | +function openstation_files_upload_owner_lock( $row, $user_id ) { | |
| 438 | 445 | if ( ! is_array( $row ) || 'upload' !== (string) ( $row['file_type'] ?? '' ) ) { |
| 439 | 446 | return true; |
| 440 | 447 | } |
| 441 | - if ( ! function_exists( 'desktop_mode_stored_files_get' ) ) { | |
| 448 | + if ( ! function_exists( 'openstation_stored_files_get' ) ) { | |
| 442 | 449 | return true; |
| 443 | 450 | } |
| 444 | - $stored = desktop_mode_stored_files_get( (int) $row['file_ref'] ); | |
| 451 | + $stored = openstation_stored_files_get( (int) $row['file_ref'] ); | |
| 445 | 452 | if ( ! $stored ) { |
| 446 | 453 | return true; |
| 447 | 454 | } |
| 448 | 455 | if ( (int) $stored['owner_id'] === (int) $user_id ) { |
| @@ -448,9 +455,9 @@ | ||
| 448 | 455 | if ( (int) $stored['owner_id'] === (int) $user_id ) { |
| 449 | 456 | return true; |
| 450 | 457 | } |
| 451 | 458 | return new WP_Error( |
| 452 | - 'desktop_mode_files_upload_owner_locked', | |
| 459 | + 'openstation_files_upload_owner_locked', | |
| 453 | 460 | __( 'Only the file’s owner can move or delete an uploaded file.', 'desktop-mode' ), |
| 454 | 461 | array( 'status' => 403 ) |
| 455 | 462 | ); |
| 456 | 463 | } |
| @@ -460,11 +467,11 @@ | ||
| 460 | 467 | * |
| 461 | 468 | * @param int $placement_id Placement id. |
| 462 | 469 | * @return array|null |
| 463 | 470 | */ |
| 464 | -function desktop_mode_files_get_placement( $placement_id ) { | |
| 471 | +function openstation_files_get_placement( $placement_id ) { | |
| 465 | 472 | global $wpdb; |
| 466 | - $tables = desktop_mode_files_table_names(); | |
| 473 | + $tables = openstation_files_table_names(); | |
| 467 | 474 | $row = $wpdb->get_row( |
| 468 | 475 | $wpdb->prepare( "SELECT * FROM {$tables['placements']} WHERE id = %d", (int) $placement_id ), |
| 469 | 476 | ARRAY_A |
| 470 | 477 | ); |
| @@ -470,14 +477,14 @@ | ||
| 470 | 477 | ); |
| 471 | 478 | if ( ! $row ) { |
| 472 | 479 | return null; |
| 473 | 480 | } |
| 474 | - return desktop_mode_files_normalize_placement_row( $row ); | |
| 481 | + return openstation_files_normalize_placement_row( $row ); | |
| 475 | 482 | } |
| 476 | 483 | |
| 477 | 484 | /** |
| 478 | 485 | * List placements for a user under a given folder (0 = desktop |
| 479 | - * root). Honors the `desktop_mode_files_query_args` filter and | |
| 486 | + * root). Honors the `openstation_files_query_args` filter and | |
| 480 | 487 | * applies the file-type's `can_read()` per row. |
| 481 | 488 | * |
| 482 | 489 | * @param int $user_id Viewer. |
| 483 | 490 | * @param int $parent_id Folder id (0 for desktop root). |
| @@ -482,9 +489,9 @@ | ||
| 482 | 489 | * @param int $user_id Viewer. |
| 483 | 490 | * @param int $parent_id Folder id (0 for desktop root). |
| 484 | 491 | * @return array[] |
| 485 | 492 | */ |
| 486 | -function desktop_mode_files_get_for_user_folder( $user_id, $parent_id = 0 ) { | |
| 493 | +function openstation_files_get_for_user_folder( $user_id, $parent_id = 0 ) { | |
| 487 | 494 | global $wpdb; |
| 488 | 495 | $user_id = (int) $user_id; |
| 489 | 496 | $parent_id = max( 0, (int) $parent_id ); |
| 490 | 497 | if ( $user_id <= 0 ) { |
| @@ -490,9 +497,9 @@ | ||
| 490 | 497 | if ( $user_id <= 0 ) { |
| 491 | 498 | return array(); |
| 492 | 499 | } |
| 493 | 500 | |
| 494 | - $tables = desktop_mode_files_table_names(); | |
| 501 | + $tables = openstation_files_table_names(); | |
| 495 | 502 | |
| 496 | 503 | // Access gate + shared-namespace decision for non-root folders. |
| 497 | 504 | // Desktop root (parent_id = 0) is always per-user. For sub- |
| 498 | 505 | // folders, the contents of a SHARED folder are visible to every |
| @@ -499,15 +506,15 @@ | ||
| 499 | 506 | // user who has at least 'read' on it — the icons inside belong |
| 500 | 507 | // to the folder, not to the user who originally placed them. |
| 501 | 508 | $share_view = false; |
| 502 | 509 | if ( $parent_id > 0 ) { |
| 503 | - $folder = desktop_mode_files_get_folder( $parent_id ); | |
| 510 | + $folder = openstation_files_get_folder( $parent_id ); | |
| 504 | 511 | if ( ! $folder ) { |
| 505 | 512 | return array(); |
| 506 | 513 | } |
| 507 | 514 | if ( (int) $folder['owner_id'] !== $user_id ) { |
| 508 | - $cap = function_exists( 'desktop_mode_folder_share_user_capability' ) | |
| 509 | - ? desktop_mode_folder_share_user_capability( $parent_id, $user_id ) | |
| 515 | + $cap = function_exists( 'openstation_folder_share_user_capability' ) | |
| 516 | + ? openstation_folder_share_user_capability( $parent_id, $user_id ) | |
| 510 | 517 | : 'none'; |
| 511 | 518 | if ( 'none' === $cap ) { |
| 512 | 519 | return array(); |
| 513 | 520 | } |
| @@ -526,9 +533,9 @@ | ||
| 526 | 533 | * @param array $args Defaults: `{ user_id, parent_id, share_view }`. |
| 527 | 534 | * @param int $user_id Viewer. |
| 528 | 535 | * @param int $parent_id Folder id. |
| 529 | 536 | */ |
| 530 | - $args = (array) apply_filters( 'desktop_mode_files_query_args', $args, $user_id, $parent_id ); | |
| 537 | + $args = (array) apply_filters( 'openstation_files_query_args', $args, $user_id, $parent_id ); | |
| 531 | 538 | |
| 532 | 539 | // Active queries always exclude trashed rows. Recycle-bin |
| 533 | 540 | // callers reach for the dedicated trash store. |
| 534 | 541 | if ( ! empty( $args['share_view'] ) ) { |
| @@ -564,10 +571,10 @@ | ||
| 564 | 571 | return array(); |
| 565 | 572 | } |
| 566 | 573 | $out = array(); |
| 567 | 574 | foreach ( $rows as $row ) { |
| 568 | - $normalized = desktop_mode_files_normalize_placement_row( $row ); | |
| 569 | - $file = desktop_mode_resolve_file( $normalized['file_type'], $normalized['file_ref'] ); | |
| 575 | + $normalized = openstation_files_normalize_placement_row( $row ); | |
| 576 | + $file = openstation_resolve_file( $normalized['file_type'], $normalized['file_ref'] ); | |
| 570 | 577 | if ( empty( $args['share_view'] ) ) { |
| 571 | 578 | // Private folder / desktop root — keep the existing |
| 572 | 579 | // per-row read filter so stale/inaccessible entities |
| 573 | 580 | // don't clutter the user's own view. |
| @@ -573,9 +580,9 @@ | ||
| 573 | 580 | // don't clutter the user's own view. |
| 574 | 581 | if ( $file && ! $file->can_read( $user_id ) ) { |
| 575 | 582 | continue; |
| 576 | 583 | } |
| 577 | - } else { | |
| 584 | + } elseif ( $file && ! $file->can_read( $user_id ) ) { | |
| 578 | 585 | // Shared folder view — every placement the OWNER chose |
| 579 | 586 | // to include is surfaced to the recipient. When the |
| 580 | 587 | // recipient lacks read on the underlying entity, we |
| 581 | 588 | // mark the row as `access_gated` so the tile renderer |
| @@ -582,11 +589,9 @@ | ||
| 582 | 589 | // can paint a lock overlay + tooltip + intercept the |
| 583 | 590 | // open. Entity-level access enforcement still happens |
| 584 | 591 | // at open time in each opener — this flag is just the |
| 585 | 592 | // pre-emptive visual cue. |
| 586 | - if ( $file && ! $file->can_read( $user_id ) ) { | |
| 587 | - $normalized['access_gated'] = true; | |
| 588 | - } | |
| 593 | + $normalized['access_gated'] = true; | |
| 589 | 594 | } |
| 590 | 595 | $out[] = $normalized; |
| 591 | 596 | } |
| 592 | 597 | return $out; |
| @@ -599,9 +604,9 @@ | ||
| 599 | 604 | * 1. Folders the viewer owns that have no placement anywhere. |
| 600 | 605 | * (Pre-fix folder-create flow could leak these; new flow |
| 601 | 606 | * writes the placement atomically.) |
| 602 | 607 | * |
| 603 | - * 2. Plugin shortcuts (`desktop_mode_register_icon()`) the | |
| 608 | + * 2. Plugin shortcuts (`openstation_register_icon()`) the | |
| 604 | 609 | * viewer hasn't placed yet. The unified-rail merge means |
| 605 | 610 | * every registered icon shows up as a `shortcut` placement |
| 606 | 611 | * on first hydrate so plugin shortcuts behave like any |
| 607 | 612 | * other tile (drag, sort, right-click, clean up). |
| @@ -606,10 +611,12 @@ | ||
| 606 | 611 | * on first hydrate so plugin shortcuts behave like any |
| 607 | 612 | * other tile (drag, sort, right-click, clean up). |
| 608 | 613 | * |
| 609 | 614 | * Idempotent on both axes: a folder/shortcut that already has |
| 610 | - * any placement is left alone. Coordinates use the column-major | |
| 611 | - * grid that `src/desktop-files/grid.ts` mirrors on the JS side. | |
| 615 | + * any placement is left alone. Coordinates come from | |
| 616 | + * `includes/desktop-files/grid.php`, which mirrors | |
| 617 | + * `src/desktop-files/grid.ts` — pitch, reading order, and the | |
| 618 | + * assumed canvas the scan wraps at. | |
| 612 | 619 | * |
| 613 | 620 | * Called by the placements list endpoint when the requested |
| 614 | 621 | * folder is the root (`parent_id=0`). |
| 615 | 622 | * |
| @@ -615,9 +622,9 @@ | ||
| 615 | 622 | * |
| 616 | 623 | * @param int $user_id Viewer. |
| 617 | 624 | * @return int Total number of orphans that were auto-placed. |
| 618 | 625 | */ |
| 619 | -function desktop_mode_files_auto_place_orphans( $user_id ) { | |
| 626 | +function openstation_files_auto_place_orphans( $user_id ) { | |
| 620 | 627 | global $wpdb; |
| 621 | 628 | $user_id = (int) $user_id; |
| 622 | 629 | if ( $user_id <= 0 ) { |
| 623 | 630 | return 0; |
| @@ -622,9 +629,9 @@ | ||
| 622 | 629 | if ( $user_id <= 0 ) { |
| 623 | 630 | return 0; |
| 624 | 631 | } |
| 625 | 632 | |
| 626 | - $tables = desktop_mode_files_table_names(); | |
| 633 | + $tables = openstation_files_table_names(); | |
| 627 | 634 | |
| 628 | 635 | // 1) Owned folders without any placement. Skip trashed folders |
| 629 | 636 | // and trashed placement rows so a recycled folder doesn't get |
| 630 | 637 | // auto-placed back on the desktop on next hydrate. |
| @@ -643,20 +650,20 @@ | ||
| 643 | 650 | ARRAY_A |
| 644 | 651 | ); |
| 645 | 652 | |
| 646 | 653 | // 2) Registered plugin shortcuts the viewer hasn't placed yet. |
| 647 | - // Pull the registered ids first, then ask the placements | |
| 648 | - // table which the viewer already has — set difference | |
| 649 | - // yields the orphans without a heavy join. | |
| 650 | - $shortcut_ids = array(); | |
| 651 | - $registry = function_exists( 'desktop_mode_desktop_icon_registry' ) | |
| 652 | - ? desktop_mode_desktop_icon_registry() | |
| 654 | + // Pull the registered ids first, then ask the placements | |
| 655 | + // table which the viewer already has — set difference | |
| 656 | + // yields the orphans without a heavy join. | |
| 657 | + $shortcut_ids = array(); | |
| 658 | + $registry = function_exists( 'openstation_desktop_icon_registry' ) | |
| 659 | + ? openstation_desktop_icon_registry() | |
| 653 | 660 | : array(); |
| 654 | 661 | if ( is_array( $registry ) ) { |
| 655 | - // Run through the same `desktop_mode_icons` filter the | |
| 662 | + // Run through the same `openstation_icons` filter the | |
| 656 | 663 | // build-payload path uses so plugins (and tests) can inject |
| 657 | 664 | // virtual entries. |
| 658 | - $registry = (array) apply_filters( 'desktop_mode_icons', $registry ); | |
| 665 | + $registry = (array) apply_filters( 'openstation_icons', $registry ); | |
| 659 | 666 | } |
| 660 | 667 | if ( is_array( $registry ) && ! empty( $registry ) ) { |
| 661 | 668 | $registered_ids = array_map( 'strval', array_keys( $registry ) ); |
| 662 | 669 | $placeholders = implode( ',', array_fill( 0, count( $registered_ids ), '%s' ) ); |
| @@ -670,9 +677,9 @@ | ||
| 670 | 677 | AND file_ref IN ($placeholders)", |
| 671 | 678 | $args |
| 672 | 679 | ) |
| 673 | 680 | ); |
| 674 | - $placed_set = array_flip( array_map( 'strval', (array) $placed_ids ) ); | |
| 681 | + $placed_set = array_flip( array_map( 'strval', (array) $placed_ids ) ); | |
| 675 | 682 | foreach ( $registered_ids as $id ) { |
| 676 | 683 | if ( ! isset( $placed_set[ $id ] ) ) { |
| 677 | 684 | $shortcut_ids[] = $id; |
| 678 | 685 | } |
| @@ -684,10 +691,11 @@ | ||
| 684 | 691 | } |
| 685 | 692 | |
| 686 | 693 | // Build an occupied set from EXISTING root placements so |
| 687 | 694 | // we never drop an orphan on top of a tile the user |
| 688 | - // already has. Cell math mirrors `src/desktop-files/grid.ts` | |
| 689 | - // (padding 16 + col 96 + row 110). | |
| 695 | + // already has. Cell math lives in | |
| 696 | + // `includes/desktop-files/grid.php`, the mirror of | |
| 697 | + // `src/desktop-files/grid.ts`. | |
| 690 | 698 | $existing = $wpdb->get_results( |
| 691 | 699 | $wpdb->prepare( |
| 692 | 700 | "SELECT x, y FROM {$tables['placements']} |
| 693 | 701 | WHERE owner_id = %d |
| @@ -696,47 +704,33 @@ | ||
| 696 | 704 | $user_id |
| 697 | 705 | ), |
| 698 | 706 | ARRAY_A |
| 699 | 707 | ); |
| 700 | - $occupied = array(); | |
| 701 | - foreach ( (array) $existing as $row ) { | |
| 702 | - $col = max( 0, (int) round( ( (int) $row['x'] - 16 ) / 96 ) ); | |
| 703 | - $row_idx = max( 0, (int) round( ( (int) $row['y'] - 16 ) / 110 ) ); | |
| 704 | - $occupied[ "$col,$row_idx" ] = true; | |
| 705 | - } | |
| 708 | + $occupied = openstation_files_grid_occupied( $existing ); | |
| 706 | 709 | |
| 707 | - $find_next = function () use ( &$occupied ) { | |
| 708 | - for ( $col = 0; $col < 999; $col++ ) { | |
| 709 | - for ( $row = 0; $row < 999; $row++ ) { | |
| 710 | - $key = "$col,$row"; | |
| 711 | - if ( ! isset( $occupied[ $key ] ) ) { | |
| 712 | - $occupied[ $key ] = true; | |
| 713 | - return array( $col, $row ); | |
| 714 | - } | |
| 715 | - } | |
| 716 | - } | |
| 717 | - return array( 0, 0 ); | |
| 718 | - }; | |
| 710 | + // The desktop reads in columns, and the scan wraps to the next one | |
| 711 | + // at the assumed canvas height rather than running a column 999 | |
| 712 | + // cells deep. The server has no viewport; a slot it invents below | |
| 713 | + // the fold is a tile the user cannot reach, because the layer that | |
| 714 | + // renders it does not scroll. | |
| 715 | + $order = openstation_files_grid_order( 0 ); | |
| 719 | 716 | |
| 720 | - $placed = 0; | |
| 717 | + $placed = 0; | |
| 721 | 718 | $emit_at = function ( $type, $ref, $col, $row ) use ( $user_id, &$occupied, &$placed ) { |
| 722 | 719 | $occupied[ "$col,$row" ] = true; |
| 723 | - $result = desktop_mode_files_place( | |
| 720 | + $result = openstation_files_place( | |
| 724 | 721 | $user_id, |
| 725 | 722 | 0, |
| 726 | 723 | $type, |
| 727 | 724 | (string) $ref, |
| 728 | - array( | |
| 729 | - 'x' => 16 + $col * 96, | |
| 730 | - 'y' => 16 + $row * 110, | |
| 731 | - ) | |
| 725 | + openstation_files_grid_cell_to_point( $col, $row ) | |
| 732 | 726 | ); |
| 733 | 727 | if ( ! is_wp_error( $result ) ) { |
| 734 | - $placed++; | |
| 728 | + ++$placed; | |
| 735 | 729 | } |
| 736 | 730 | }; |
| 737 | - $emit_next = function ( $type, $ref ) use ( $find_next, $emit_at ) { | |
| 738 | - list( $col, $row ) = $find_next(); | |
| 731 | + $emit_next = function ( $type, $ref ) use ( &$occupied, $order, $emit_at ) { | |
| 732 | + list( $col, $row ) = openstation_files_grid_next_free( $occupied, $order ); | |
| 739 | 733 | $emit_at( $type, $ref, $col, $row ); |
| 740 | 734 | }; |
| 741 | 735 | |
| 742 | 736 | // Pinned shortcuts get reserved top-left slots. Anchored to |
| @@ -761,9 +755,9 @@ | ||
| 761 | 755 | // client-side override anyway, but a future cleanup pass |
| 762 | 756 | // can compact the column. |
| 763 | 757 | $occupied[ "0,$pinned_idx" ] = true; |
| 764 | 758 | $emit_at( 'shortcut', $id, 0, $pinned_idx ); |
| 765 | - $pinned_idx++; | |
| 759 | + ++$pinned_idx; | |
| 766 | 760 | } |
| 767 | 761 | |
| 768 | 762 | foreach ( $folder_rows as $row ) { |
| 769 | 763 | $emit_next( 'folder', $row['id'] ); |
| @@ -779,15 +773,15 @@ | ||
| 779 | 773 | |
| 780 | 774 | /** |
| 781 | 775 | * Backwards-compat alias for the older folder-only name. |
| 782 | 776 | * |
| 783 | - * @deprecated Use {@see desktop_mode_files_auto_place_orphans}. | |
| 777 | + * @deprecated Use {@see openstation_files_auto_place_orphans}. | |
| 784 | 778 | * |
| 785 | 779 | * @param int $user_id Viewer. |
| 786 | 780 | * @return int |
| 787 | 781 | */ |
| 788 | -function desktop_mode_files_auto_place_orphan_folders( $user_id ) { | |
| 789 | - return desktop_mode_files_auto_place_orphans( $user_id ); | |
| 782 | +function openstation_files_auto_place_orphan_folders( $user_id ) { | |
| 783 | + return openstation_files_auto_place_orphans( $user_id ); | |
| 790 | 784 | } |
| 791 | 785 | |
| 792 | 786 | /** |
| 793 | 787 | * Coerce wpdb's stringly-typed row into typed values + decoded |
| @@ -797,9 +791,9 @@ | ||
| 797 | 791 | * |
| 798 | 792 | * @param array $row Raw wpdb row. |
| 799 | 793 | * @return array |
| 800 | 794 | */ |
| 801 | -function desktop_mode_files_normalize_placement_row( $row ) { | |
| 795 | +function openstation_files_normalize_placement_row( $row ) { | |
| 802 | 796 | $meta_raw = isset( $row['meta'] ) ? (string) $row['meta'] : ''; |
| 803 | 797 | $meta = '' !== $meta_raw ? json_decode( $meta_raw, true ) : null; |
| 804 | 798 | return array( |
| 805 | 799 | 'id' => (int) $row['id'], |
| @@ -804,9 +798,9 @@ | ||
| 804 | 798 | return array( |
| 805 | 799 | 'id' => (int) $row['id'], |
| 806 | 800 | 'owner_id' => (int) $row['owner_id'], |
| 807 | 801 | // `updated_by` is v10. Null on legacy rows — callers that |
| 808 | - // need the actor (e.g. `desktop_mode_files_check_if_match`) | |
| 802 | + // need the actor (e.g. `openstation_files_check_if_match`) | |
| 809 | 803 | // fall back to `owner_id` when this is null/missing. |
| 810 | 804 | 'updated_by' => isset( $row['updated_by'] ) ? (int) $row['updated_by'] : null, |
| 811 | 805 | 'parent_id' => (int) $row['parent_id'], |
| 812 | 806 | 'file_type' => (string) $row['file_type'], |
| @@ -825,17 +819,17 @@ | ||
| 825 | 819 | * Invariant (enforced by callers): tombstones may exist only for |
| 826 | 820 | * ids of PERMANENTLY-DELETED rows. Never write one for a soft- |
| 827 | 821 | * trashed row — soft-trash is reversible and the heartbeat already |
| 828 | 822 | * surfaces it via the `trashed_at_ms IS NOT NULL` query in |
| 829 | - * `desktop_mode_files_compute_heartbeat_delta`. A tombstone on a | |
| 823 | + * `openstation_files_compute_heartbeat_delta`. A tombstone on a | |
| 830 | 824 | * soft-trashed row lingers past restore and tells clients the row |
| 831 | 825 | * is gone while it is in fact alive — see the "shared folder |
| 832 | 826 | * disappears on refresh" bug. |
| 833 | 827 | * |
| 834 | - * Pair every revival path (`desktop_mode_files_restore_placement`, | |
| 835 | - * `desktop_mode_files_restore_folder`, and the duplicate-key | |
| 836 | - * revival branch in `desktop_mode_files_place`) with | |
| 837 | - * {@see desktop_mode_files_clear_tombstones_for} so a row coming | |
| 828 | + * Pair every revival path (`openstation_files_restore_placement`, | |
| 829 | + * `openstation_files_restore_folder`, and the duplicate-key | |
| 830 | + * revival branch in `openstation_files_place`) with | |
| 831 | + * {@see openstation_files_clear_tombstones_for} so a row coming | |
| 838 | 832 | * back to life never carries lingering tombstones from a previous |
| 839 | 833 | * removal that turned out to be reversible. |
| 840 | 834 | * |
| 841 | 835 | * @param string $kind 'placement' | 'folder'. |
| @@ -840,17 +834,17 @@ | ||
| 840 | 834 | * |
| 841 | 835 | * @param string $kind 'placement' | 'folder'. |
| 842 | 836 | * @param int $ref Removed id. |
| 843 | 837 | */ |
| 844 | -function desktop_mode_files_write_tombstone( $kind, $ref ) { | |
| 838 | +function openstation_files_write_tombstone( $kind, $ref ) { | |
| 845 | 839 | global $wpdb; |
| 846 | - $tables = desktop_mode_files_table_names(); | |
| 840 | + $tables = openstation_files_table_names(); | |
| 847 | 841 | $wpdb->insert( |
| 848 | 842 | $tables['tombstones'], |
| 849 | 843 | array( |
| 850 | 844 | 'kind' => (string) $kind, |
| 851 | 845 | 'ref_id' => (int) $ref, |
| 852 | - 'removed_at_ms' => desktop_mode_files_now_ms(), | |
| 846 | + 'removed_at_ms' => openstation_files_now_ms(), | |
| 853 | 847 | ), |
| 854 | 848 | array( '%s', '%d', '%d' ) |
| 855 | 849 | ); |
| 856 | 850 | } |
| @@ -865,15 +859,15 @@ | ||
| 865 | 859 | * |
| 866 | 860 | * @param string $kind 'placement' | 'folder'. |
| 867 | 861 | * @param int $ref_id Row id whose tombstones should be dropped. |
| 868 | 862 | */ |
| 869 | -function desktop_mode_files_clear_tombstones_for( $kind, $ref_id ) { | |
| 863 | +function openstation_files_clear_tombstones_for( $kind, $ref_id ) { | |
| 870 | 864 | global $wpdb; |
| 871 | 865 | $ref_id = (int) $ref_id; |
| 872 | 866 | if ( $ref_id <= 0 ) { |
| 873 | 867 | return; |
| 874 | 868 | } |
| 875 | - $tables = desktop_mode_files_table_names(); | |
| 869 | + $tables = openstation_files_table_names(); | |
| 876 | 870 | $wpdb->delete( |
| 877 | 871 | $tables['tombstones'], |
| 878 | 872 | array( |
| 879 | 873 | 'kind' => (string) $kind, |
| @@ -888,15 +882,15 @@ | ||
| 888 | 882 | * the retention window when the Heartbeat sync lands; for now 7d |
| 889 | 883 | * is plenty since a client that's been offline that long will |
| 890 | 884 | * always need a full REST resync anyway. |
| 891 | 885 | */ |
| 892 | -function desktop_mode_files_prune_tombstones() { | |
| 886 | +function openstation_files_prune_tombstones() { | |
| 893 | 887 | global $wpdb; |
| 894 | - $tables = desktop_mode_files_table_names(); | |
| 895 | - $cutoff = desktop_mode_files_now_ms() - ( 7 * DAY_IN_SECONDS * 1000 ); | |
| 888 | + $tables = openstation_files_table_names(); | |
| 889 | + $cutoff = openstation_files_now_ms() - ( 7 * DAY_IN_SECONDS * 1000 ); | |
| 896 | 890 | $wpdb->query( $wpdb->prepare( "DELETE FROM {$tables['tombstones']} WHERE removed_at_ms < %d", $cutoff ) ); |
| 897 | 891 | } |
| 898 | -add_action( 'desktop_mode_files_daily_prune', 'desktop_mode_files_prune_tombstones' ); | |
| 892 | +add_action( 'desktop_mode_files_daily_prune', 'openstation_files_prune_tombstones' ); | |
| 899 | 893 | |
| 900 | 894 | /** |
| 901 | 895 | * Schedule the daily prune. Hooked on `init` and idempotent via |
| 902 | 896 | * wp_next_scheduled(), so a manual file-copy install (no activation |
| @@ -901,14 +895,14 @@ | ||
| 901 | 895 | * Schedule the daily prune. Hooked on `init` and idempotent via |
| 902 | 896 | * wp_next_scheduled(), so a manual file-copy install (no activation |
| 903 | 897 | * hook) still gets the cron event. |
| 904 | 898 | */ |
| 905 | -function desktop_mode_files_schedule_prune() { | |
| 899 | +function openstation_files_schedule_prune() { | |
| 906 | 900 | if ( ! wp_next_scheduled( 'desktop_mode_files_daily_prune' ) ) { |
| 907 | 901 | wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'desktop_mode_files_daily_prune' ); |
| 908 | 902 | } |
| 909 | 903 | } |
| 910 | -add_action( 'init', 'desktop_mode_files_schedule_prune' ); | |
| 904 | +add_action( 'init', 'openstation_files_schedule_prune' ); | |
| 911 | 905 | |
| 912 | 906 | /** |
| 913 | 907 | * Walk the folder-parentage chain upward from `$target_parent_id` and |
| 914 | 908 | * return `true` when `$moving_folder_id` appears anywhere in it — |
| @@ -931,9 +925,9 @@ | ||
| 931 | 925 | * @param int $moving_folder_id Folder being moved (its `folders.id`). |
| 932 | 926 | * @param int $target_parent_id New container folder id (0 = desktop root). |
| 933 | 927 | * @return bool True when the move would create a cycle. |
| 934 | 928 | */ |
| 935 | -function desktop_mode_files_would_create_folder_cycle( $user_id, $moving_folder_id, $target_parent_id ) { | |
| 929 | +function openstation_files_would_create_folder_cycle( $user_id, $moving_folder_id, $target_parent_id ) { | |
| 936 | 930 | $moving_folder_id = (int) $moving_folder_id; |
| 937 | 931 | $target_parent_id = (int) $target_parent_id; |
| 938 | 932 | $user_id = (int) $user_id; |
| 939 | 933 | if ( $moving_folder_id <= 0 || $target_parent_id <= 0 || $user_id <= 0 ) { |
| @@ -942,9 +936,9 @@ | ||
| 942 | 936 | if ( $moving_folder_id === $target_parent_id ) { |
| 943 | 937 | return true; |
| 944 | 938 | } |
| 945 | 939 | global $wpdb; |
| 946 | - $tables = desktop_mode_files_table_names(); | |
| 940 | + $tables = openstation_files_table_names(); | |
| 947 | 941 | $visited = array(); |
| 948 | 942 | $cursor = $target_parent_id; |
| 949 | 943 | // Hard cap to defend against catastrophically deep trees too — |
| 950 | 944 | // real installs won't approach 256. |