PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.7
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 0.8.7 All 34 releases
desktop-mode / includes / desktop-files / sharing.php

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

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