| @@ -409,8 +409,44 @@ | ||
| 409 | 409 | } |
| 410 | 410 | add_filter( 'openstation_shell_config', 'openstation_files_inject_boot_placements', 20 ); |
| 411 | 411 | |
| 412 | 412 | /** |
| 413 | + * Inline the viewer's visible folder rows into the boot-time shell | |
| 414 | + * config, the same way the root placements are. | |
| 415 | + * | |
| 416 | + * The client keeps a folders map alongside its placements, and | |
| 417 | + * anything that needs to know a folder's OWNER reads it there — | |
| 418 | + * notably the "Share folder" title-bar button, which is owner-only | |
| 419 | + * and has nothing but a window id to work from. Nothing on the | |
| 420 | + * normal boot path filled that map: placement hydration populates | |
| 421 | + * placements, and `listFolders()` only ran after a create, a rename | |
| 422 | + * or an untrash. So a plain reload left every folder ownerless, and | |
| 423 | + * the owner of a folder lost the one control that manages its | |
| 424 | + * sharing until something happened to repopulate the map. | |
| 425 | + * | |
| 426 | + * Mirrors GET /folders exactly (same visibility resolution — owned | |
| 427 | + * folders plus accepted shares plus `share_mode='all'` — same | |
| 428 | + * shape), and the client consumes it one-shot, so any later | |
| 429 | + * re-hydration still goes through REST for fresh state. | |
| 430 | + * | |
| 431 | + * @param array $config Shell config. | |
| 432 | + * @return array | |
| 433 | + */ | |
| 434 | +function openstation_files_inject_boot_folders( $config ) { | |
| 435 | + $user_id = get_current_user_id(); | |
| 436 | + if ( $user_id <= 0 ) { | |
| 437 | + return $config; | |
| 438 | + } | |
| 439 | + $out = array(); | |
| 440 | + foreach ( openstation_files_get_visible_folders( $user_id ) as $row ) { | |
| 441 | + $out[] = openstation_files_shape_folder( $row ); | |
| 442 | + } | |
| 443 | + $config['filesBootFolders'] = $out; | |
| 444 | + return $config; | |
| 445 | +} | |
| 446 | +add_filter( 'openstation_shell_config', 'openstation_files_inject_boot_folders', 20 ); | |
| 447 | + | |
| 448 | +/** | |
| 413 | 449 | * GET /placements |
| 414 | 450 | */ |
| 415 | 451 | function openstation_files_rest_list_placements( WP_REST_Request $req ) { |
| 416 | 452 | $user_id = get_current_user_id(); |
| @@ -733,28 +769,13 @@ | ||
| 733 | 769 | 'shareMode' => (string) $row['share_mode'], |
| 734 | 770 | 'shareMeta' => isset( $row['share_meta'] ) ? $row['share_meta'] : null, |
| 735 | 771 | 'updatedAtMs' => (int) $row['updated_at_ms'], |
| 736 | 772 | ); |
| 737 | - if ( function_exists( 'openstation_files_get_folder_shares' ) ) { | |
| 738 | - $shares = openstation_files_get_folder_shares( (int) $row['id'] ); | |
| 739 | - $accepted_count = 0; | |
| 740 | - $has_all = 'all' === (string) $row['share_mode']; | |
| 741 | - foreach ( $shares as $s ) { | |
| 742 | - if ( 'accepted' === $s['state'] ) { | |
| 743 | - ++$accepted_count; | |
| 744 | - } | |
| 745 | - } | |
| 746 | - // `shared` is viewer-agnostic — recipients need it for the | |
| 747 | - // shared-folder badge. The recipient COUNT is owner-internal | |
| 748 | - // (the dedicated shares endpoint gates the full roster on | |
| 749 | - // `share_can_manage`), so only managers get the real number; | |
| 750 | - // every other viewer sees `0`, keeping the wire shape stable. | |
| 751 | - $can_manage = function_exists( 'openstation_files_share_can_manage' ) | |
| 752 | - && openstation_files_share_can_manage( (int) $row['id'], get_current_user_id() ); | |
| 753 | - $shape['shareSummary'] = array( | |
| 754 | - 'shared' => $has_all || $accepted_count > 0, | |
| 755 | - 'recipientCount' => $can_manage ? $accepted_count + ( $has_all ? 1 : 0 ) : 0, | |
| 756 | - ); | |
| 773 | + // Same summary `OpenStation_Folder_File::serialize()` puts on a | |
| 774 | + // folder PLACEMENT, so a tile paints identically whichever | |
| 775 | + // response it came from. | |
| 776 | + if ( function_exists( 'openstation_files_folder_share_summary' ) ) { | |
| 777 | + $shape['shareSummary'] = openstation_files_folder_share_summary( $row ); | |
| 757 | 778 | } |
| 758 | 779 | return $shape; |
| 759 | 780 | } |
| 760 | 781 | |
| @@ -834,9 +855,9 @@ | ||
| 834 | 855 | } |
| 835 | 856 | } |
| 836 | 857 | $actor_payload = array( |
| 837 | 858 | 'id' => $viewer_can_see ? $actor_id : 0, |
| 838 | - 'name' => $viewer_can_see && $actor ? $actor->display_name : '', | |
| 859 | + 'name' => $viewer_can_see && $actor ? openstation_plain_text_title( $actor->display_name ) : '', | |
| 839 | 860 | 'avatar' => $viewer_can_see && $actor ? get_avatar_url( $actor->ID, array( 'size' => 32 ) ) : '', |
| 840 | 861 | ); |
| 841 | 862 | |
| 842 | 863 | return new WP_Error( |
| @@ -880,9 +901,9 @@ | ||
| 880 | 901 | ); |
| 881 | 902 | if ( 'user' === $row['principal_type'] ) { |
| 882 | 903 | $uid = (int) $row['principal_ref']; |
| 883 | 904 | $user = $uid > 0 ? get_userdata( $uid ) : null; |
| 884 | - $shape['displayName'] = $user ? $user->display_name : ''; | |
| 905 | + $shape['displayName'] = $user ? openstation_plain_text_title( $user->display_name ) : ''; | |
| 885 | 906 | $shape['avatarUrl'] = $user ? get_avatar_url( $uid, array( 'size' => 48 ) ) : ''; |
| 886 | 907 | } else { |
| 887 | 908 | $roles = wp_roles(); |
| 888 | 909 | $info = $roles && isset( $roles->roles[ $row['principal_ref'] ] ) ? $roles->roles[ $row['principal_ref'] ] : null; |
| @@ -1200,9 +1221,9 @@ | ||
| 1200 | 1221 | // broader than needed for a share picker. Matches the |
| 1201 | 1222 | // `slug` field WP's own `/wp/v2/users` endpoint surfaces. |
| 1202 | 1223 | $out[] = array( |
| 1203 | 1224 | 'id' => (int) $user->ID, |
| 1204 | - 'name' => (string) $user->display_name, | |
| 1225 | + 'name' => openstation_plain_text_title( $user->display_name ), | |
| 1205 | 1226 | 'slug' => (string) $user->user_nicename, |
| 1206 | 1227 | 'avatarUrl' => get_avatar_url( $user->ID, array( 'size' => 48 ) ), |
| 1207 | 1228 | ); |
| 1208 | 1229 | } |