PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.13
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.13
1.1.13 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 All 37 releases
← All changes | includes/desktop-files/rest.php +63 -29 1.0.0 → 1.1.13 View file →
@@ -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;
@@ -1162,18 +1183,31 @@
1162 1183 // in the dropdown on it.
1163 1184 $exclude[] = (int) get_current_user_id();
1164 1185 $exclude = array_values( array_unique( array_filter( $exclude ) ) );
1165 1186
1187 + // Eligibility lives in the query, not only in the loop below:
1188 + // with `number` applied first, a page of 20 Subscribers (or
1189 + // agents) sorting ahead of the editors came back empty after
1190 + // the per-row check, and the picker showed nobody at all. The
1191 + // loop still re-checks every row, because the filter below can
1192 + // rewrite these args.
1166 1193 $args = array(
1167 - 'number' => 20,
1168 - 'orderby' => 'display_name',
1169 - 'order' => 'ASC',
1170 - 'exclude' => $exclude,
1194 + 'number' => 20,
1195 + 'orderby' => 'display_name',
1196 + 'order' => 'ASC',
1197 + 'exclude' => $exclude,
1198 + 'capability' => 'edit_posts',
1199 + 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- agents are excluded in SQL so they cannot use up the page.
1200 + array(
1201 + 'key' => OPENSTATION_AGENT_USER_MARKER_META,
1202 + 'compare' => 'NOT EXISTS',
1203 + ),
1204 + ),
1171 1205 // `fields => 'all'` returns full WP_User objects so the
1172 1206 // capability check below resolves role caps correctly. A
1173 1207 // stdClass with stripped fields breaks `user_can()` on
1174 1208 // some WordPress versions and silently drops every row.
1175 - 'fields' => 'all',
1209 + 'fields' => 'all',
1176 1210 );
1177 1211 if ( '' !== $q ) {
1178 1212 $args['search'] = '*' . $q . '*';
1179 1213 $args['search_columns'] = array( 'user_login', 'user_email', 'display_name', 'user_nicename' );
@@ -1190,9 +1224,9 @@
1190 1224 $query = new WP_User_Query( $args );
1191 1225 $users = $query->get_results();
1192 1226 $out = array();
1193 1227 foreach ( (array) $users as $user ) {
1194 - if ( ! user_can( $user, 'edit_posts' ) ) {
1228 + if ( ! openstation_files_share_user_is_eligible( $user ) ) {
1195 1229 continue;
1196 1230 }
1197 1231 // Disambiguation handle uses `user_nicename` (the public
1198 1232 // URL slug) instead of `user_login` — the login is the auth
@@ -1200,9 +1234,9 @@
1200 1234 // broader than needed for a share picker. Matches the
1201 1235 // `slug` field WP's own `/wp/v2/users` endpoint surfaces.
1202 1236 $out[] = array(
1203 1237 'id' => (int) $user->ID,
1204 - 'name' => (string) $user->display_name,
1238 + 'name' => openstation_plain_text_title( $user->display_name ),
1205 1239 'slug' => (string) $user->user_nicename,
1206 1240 'avatarUrl' => get_avatar_url( $user->ID, array( 'size' => 48 ) ),
1207 1241 );
1208 1242 }