PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.8
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.8
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 0.8.7 0.8.6 All 33 releases
desktop-mode / includes / desktop-files / sharing.php

sharing.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.8, at includes/desktop-files/sharing.php

177 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Folder sharing visibility logic.
4 *
5 * Computes which folders a viewer can see from each folder's
6 * `share_mode` plus the shares / decisions tables:
7 *
8 * - `private` — owner only.
9 * - `users` / `roles` — owner + principals holding an accepted
10 * grant in the `_desktop_mode_folder_shares` table (role grants
11 * additionally require a per-user accepted row in the decisions
12 * table). The folders row's `share_meta` column is
13 * diagnostic-only and is never consulted for visibility.
14 * - `all` — every openstation user on the site.
15 *
16 * Hooked at priority 5 on `openstation_files_visible_folders`
17 * so plugins layering custom share modes (registered via
18 * `openstation_files_share_modes`) can run later in the chain
19 * without competing for the early slot.
20 *
21 * @package OpenStation
22 */
23
24 defined( 'ABSPATH' ) || exit;
25
26 /**
27 * Filter callback that augments the owner-only list with folders
28 * the viewer can see by virtue of a non-private share mode.
29 *
30 * @param array $owned Owner-only folders (default from the store).
31 * @param int $user_id Viewer.
32 * @return array
33 */
34 function openstation_files_compute_visible_folders( $owned, $user_id ) {
35 global $wpdb;
36 $user_id = (int) $user_id;
37 if ( $user_id <= 0 ) {
38 return is_array( $owned ) ? $owned : array();
39 }
40
41 $tables = openstation_files_table_names();
42 $user = get_userdata( $user_id );
43 $roles = $user ? array_values( (array) $user->roles ) : array();
44
45 // Source 1 — `share_mode='all'`. Pull straight from the folders
46 // table; the shares table never carries 'all' rows.
47 $all_rows = $wpdb->get_results(
48 $wpdb->prepare(
49 "SELECT * FROM {$tables['folders']}
50 WHERE owner_id <> %d
51 AND share_mode = 'all'
52 AND trashed_at_ms IS NULL",
53 $user_id
54 ),
55 ARRAY_A
56 );
57
58 // Source 2 — accepted user-principal shares. State lives on the
59 // shares row: once the recipient clicks Accept we flip
60 // `state='accepted'` directly.
61 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
62 $user_share_rows = $wpdb->get_results(
63 $wpdb->prepare(
64 "SELECT DISTINCT f.* FROM {$tables['folders']} f
65 INNER JOIN {$tables['shares']} s ON s.folder_id = f.id
66 WHERE f.owner_id <> %d
67 AND f.trashed_at_ms IS NULL
68 AND s.state = 'accepted'
69 AND s.principal_type = 'user'
70 AND s.principal_ref = %s",
71 $user_id,
72 (string) $user_id
73 ),
74 ARRAY_A
75 );
76
77 // Source 2b — role-principal shares the viewer has individually
78 // accepted via the per-user decisions table. The shares row
79 // itself intentionally stays `state='pending'` for role-principal
80 // invites (we don't flip a role share to 'accepted' on behalf of
81 // every member of the role — that would be a "first to click
82 // decides for all" bug). The per-user acceptance lives in the
83 // decisions table, mirroring the resolution logic in
84 // `openstation_folder_share_user_capability`.
85 //
86 // Without this join the role recipient could see the folder via
87 // REST `list_placements` (which routes through
88 // `_user_capability`, which DOES consult decisions) but their
89 // heartbeat would miss live updates because the heartbeat
90 // short-circuits on `compute_visible_folders` — leaving new
91 // files the owner added invisible until F5.
92 $role_share_rows = array();
93 if ( ! empty( $roles ) ) {
94 $placeholders = implode( ',', array_fill( 0, count( $roles ), '%s' ) );
95 $role_args = array_merge( array( $user_id, $user_id ), array_map( 'strval', $roles ) );
96 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
97 $role_share_rows = $wpdb->get_results(
98 $wpdb->prepare(
99 "SELECT DISTINCT f.* FROM {$tables['folders']} f
100 INNER JOIN {$tables['shares']} s ON s.folder_id = f.id
101 INNER JOIN {$tables['decisions']} d
102 ON d.share_id = s.id
103 AND d.user_id = %d
104 AND d.state = 'accepted'
105 WHERE f.owner_id <> %d
106 AND f.trashed_at_ms IS NULL
107 AND s.principal_type = 'role'
108 AND s.principal_ref IN ($placeholders)",
109 $role_args
110 ),
111 ARRAY_A
112 );
113 }
114
115 $share_rows = array_merge( (array) $user_share_rows, (array) $role_share_rows );
116
117 $visible = is_array( $owned ) ? $owned : array();
118 $seen_ids = array();
119 foreach ( $visible as $row ) {
120 $seen_ids[ (int) $row['id'] ] = true;
121 }
122 foreach ( array_merge( (array) $all_rows, (array) $share_rows ) as $raw ) {
123 $row = openstation_files_normalize_folder_row( $raw );
124 $id = (int) $row['id'];
125 if ( isset( $seen_ids[ $id ] ) ) {
126 continue;
127 }
128 if ( openstation_files_user_can_see_folder( $row, $user_id, $roles ) ) {
129 $visible[] = $row;
130 $seen_ids[ $id ] = true;
131 }
132 }
133 return $visible;
134 }
135 add_filter( 'openstation_files_visible_folders', 'openstation_files_compute_visible_folders', 5, 2 );
136
137 /**
138 * Whether the viewer's identity satisfies a folder's share rules.
139 *
140 * @param array $folder Normalized folder row.
141 * @param int $user_id Viewer.
142 * @param string[] $user_roles Viewer's roles.
143 * @return bool
144 */
145 function openstation_files_user_can_see_folder( $folder, $user_id, $user_roles ) {
146 $mode = (string) $folder['share_mode'];
147
148 // Owner always sees the folder.
149 if ( (int) $folder['owner_id'] === (int) $user_id ) {
150 $can = true;
151 } elseif ( 'all' === $mode ) {
152 $can = true;
153 } else {
154 // Non-owner viewer: the shares table is the single source
155 // of truth. `share_meta` on the folders row is diagnostic
156 // only — it is never consulted for visibility. (Earlier
157 // drafts had a fallback that silently re-granted access
158 // to revoked recipients; reviewer caught the
159 // revocation-bypass and we dropped the fallback before
160 // the feature shipped.)
161 $cap = openstation_folder_share_user_capability( (int) $folder['id'], (int) $user_id );
162 $can = 'none' !== $cap;
163 }
164
165 /**
166 * Filter the per-folder visibility decision. Plugins layering
167 * custom share modes (e.g. 'team', 'workspace') can compute
168 * `$can` here.
169 *
170 * @param bool $can Default decision.
171 * @param array $folder Folder row.
172 * @param int $user_id Viewer.
173 * @param string[] $roles Viewer's roles.
174 */
175 return (bool) apply_filters( 'openstation_files_user_can_see_folder', $can, $folder, $user_id, $user_roles );
176 }
177