PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
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 0.8.9 0.8.8 All 35 releases
← All changes | includes/desktop-files/heartbeat.php +72 -64 0.9.8 → 1.1.11 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Files Heartbeat sync (PHP).
3 + * OpenStation — Files Heartbeat sync (PHP).
4 4 *
5 5 * Piggybacks on the existing WordPress Heartbeat tick — the same
6 6 * channel `presence.php` uses — so connected clients see folder
7 7 * sharing changes and other users' placement edits inside one
@@ -6,13 +6,13 @@
6 6 * channel `presence.php` uses — so connected clients see folder
7 7 * sharing changes and other users' placement edits inside one
8 8 * cross-feature poll instead of N parallel ones.
9 9 *
10 - * Wire format. Client sends `desktop_mode_files_subscribe` keyed
10 + * Wire format. Client sends `openstation_files_subscribe` keyed
11 11 * to three version markers:
12 12 *
13 13 * {
14 - * desktop_mode_files_subscribe: {
14 + * openstation_files_subscribe: {
15 15 * folderVersions: { '<folderId>': lastSeenUpdatedAtMs, ... },
16 16 * placementsVersion: lastSeenUpdatedAtMs,
17 17 * sharesVersion: lastSeenInvitedAtMs
18 18 * }
@@ -19,9 +19,9 @@
19 19 * }
20 20 *
21 21 * Server responds with deltas + tombstones:
22 22 *
23 - * desktop_mode_files: {
23 + * openstation_files: {
24 24 * placements: [ <RestPlacementShape> ], // upserts
25 25 * folders: [ <RestFolderShape> ], // upserts (incl. share-mode flips)
26 26 * removed: {
27 27 * placements: [ ids ],
@@ -33,14 +33,14 @@
33 33 * serverTimeMs: int,
34 34 * truncated: bool
35 35 * }
36 36 *
37 - * Truncation kicks in when more than `desktop_mode_files_heartbeat_max_rows`
37 + * Truncation kicks in when more than `openstation_files_heartbeat_max_rows`
38 38 * (default 200) rows match — clients fall back to a full REST
39 39 * resync. The default cap is per-payload, not per-folder, so a
40 40 * massive shared folder doesn't starve other folders' deltas.
41 41 *
42 - * @package WPDesktopMode
42 + * @package OpenStation
43 43 */
44 44
45 45 defined( 'ABSPATH' ) || exit;
46 46
@@ -48,20 +48,20 @@
48 48 * @param array $response Pre-filtered response.
49 49 * @param array $data Client-sent payload.
50 50 * @return array
51 51 */
52 -function desktop_mode_files_heartbeat_received( $response, $data ) {
52 +function openstation_files_heartbeat_received( $response, $data ) {
53 53 if ( ! is_array( $response ) ) {
54 54 $response = array();
55 55 }
56 - if ( empty( $data['desktop_mode_files_subscribe'] ) || ! is_array( $data['desktop_mode_files_subscribe'] ) ) {
56 + if ( empty( $data['openstation_files_subscribe'] ) || ! is_array( $data['openstation_files_subscribe'] ) ) {
57 57 return $response;
58 58 }
59 - if ( ! function_exists( 'desktop_mode_is_enabled' ) || ! desktop_mode_is_enabled() ) {
59 + if ( ! function_exists( 'openstation_is_enabled' ) || ! openstation_is_enabled() ) {
60 60 return $response;
61 61 }
62 62
63 - $sub = $data['desktop_mode_files_subscribe'];
63 + $sub = $data['openstation_files_subscribe'];
64 64 $folder_v = isset( $sub['folderVersions'] ) && is_array( $sub['folderVersions'] )
65 65 ? $sub['folderVersions']
66 66 : array();
67 67 $plc_v = isset( $sub['placementsVersion'] ) ? (int) $sub['placementsVersion'] : 0;
@@ -78,11 +78,11 @@
78 78 * intranets where a fatter Heartbeat is fine.
79 79 *
80 80 * @param int $cap Default 200.
81 81 */
82 - $cap = max( 1, (int) apply_filters( 'desktop_mode_files_heartbeat_max_rows', 200 ) );
82 + $cap = max( 1, (int) apply_filters( 'openstation_files_heartbeat_max_rows', 200 ) );
83 83
84 - $response['desktop_mode_files'] = desktop_mode_files_compute_heartbeat_delta(
84 + $response['openstation_files'] = openstation_files_compute_heartbeat_delta(
85 85 $user_id,
86 86 $folder_v,
87 87 $plc_v,
88 88 $cap,
@@ -89,9 +89,9 @@
89 89 $shr_v
90 90 );
91 91 return $response;
92 92 }
93 -add_filter( 'heartbeat_received', 'desktop_mode_files_heartbeat_received', 5, 2 );
93 +add_filter( 'heartbeat_received', 'openstation_files_heartbeat_received', 5, 2 );
94 94
95 95 /**
96 96 * Compute the delta payload for a viewer.
97 97 *
@@ -105,19 +105,19 @@
105 105 * to invites the client hasn't seen
106 106 * yet. Defaults to `0` (deliver all).
107 107 * @return array
108 108 */
109 -function desktop_mode_files_compute_heartbeat_delta( $user_id, $folder_versions, $placements_version, $cap, $shares_version = 0 ) {
109 +function openstation_files_compute_heartbeat_delta( $user_id, $folder_versions, $placements_version, $cap, $shares_version = 0 ) {
110 110 global $wpdb;
111 111
112 - $tables = desktop_mode_files_table_names();
112 + $tables = openstation_files_table_names();
113 113 $truncated = false;
114 114
115 115 // 1) Visible folders the viewer should know about. We send
116 - // the FULL row when its `updated_at_ms` exceeds whatever
117 - // the client last saw (or the client doesn't know about
118 - // it at all).
119 - $visible = desktop_mode_files_get_visible_folders( $user_id );
116 + // the FULL row when its `updated_at_ms` exceeds whatever
117 + // the client last saw (or the client doesn't know about
118 + // it at all).
119 + $visible = openstation_files_get_visible_folders( $user_id );
120 120 $folder_upserts = array();
121 121 foreach ( $visible as $row ) {
122 122 $id = (int) $row['id'];
123 123 $client_ts = isset( $folder_versions[ (string) $id ] )
@@ -123,9 +123,9 @@
123 123 $client_ts = isset( $folder_versions[ (string) $id ] )
124 124 ? (int) $folder_versions[ (string) $id ]
125 125 : 0;
126 126 if ( (int) $row['updated_at_ms'] > $client_ts ) {
127 - $folder_upserts[] = desktop_mode_files_shape_folder( $row );
127 + $folder_upserts[] = openstation_files_shape_folder( $row );
128 128 if ( count( $folder_upserts ) >= $cap ) {
129 129 $truncated = true;
130 130 break;
131 131 }
@@ -132,14 +132,17 @@
132 132 }
133 133 }
134 134
135 135 // 2) Placement upserts the viewer can see. We pull anything
136 - // written since `placements_version` whose owner is the
137 - // viewer (their own desktop) OR which lives in a folder
138 - // the viewer can see (shared content).
139 - $visible_folder_ids = array_map( static function ( $f ) {
140 - return (int) $f['id'];
141 - }, $visible );
136 + // written since `placements_version` whose owner is the
137 + // viewer (their own desktop) OR which lives in a folder
138 + // the viewer can see (shared content).
139 + $visible_folder_ids = array_map(
140 + static function ( $f ) {
141 + return (int) $f['id'];
142 + },
143 + $visible
144 + );
142 145 // Always include the desktop root (parent_id=0) for the viewer.
143 146 $placement_upserts = array();
144 147 if ( ! $truncated ) {
145 148 // Owner-or-visible-folder filter, expressed as a SINGLE
@@ -193,16 +196,16 @@
193 196 ARRAY_A
194 197 );
195 198 }
196 199 foreach ( (array) $rows as $row ) {
197 - $row = desktop_mode_files_normalize_placement_row( $row );
200 + $row = openstation_files_normalize_placement_row( $row );
198 201 // Per-placement read gate: shared folder shouldn't
199 202 // surface a row the viewer's `can_read()` rejects.
200 - $file = desktop_mode_resolve_file( $row['file_type'], $row['file_ref'] );
203 + $file = openstation_resolve_file( $row['file_type'], $row['file_ref'] );
201 204 if ( $file && ! $file->can_read( $user_id ) ) {
202 205 continue;
203 206 }
204 - $placement_upserts[] = desktop_mode_files_shape_placement( $row );
207 + $placement_upserts[] = openstation_files_shape_placement( $row );
205 208 }
206 209 if ( count( $placement_upserts ) >= $cap ) {
207 210 $truncated = true;
208 211 }
@@ -208,9 +211,9 @@
208 211 }
209 212 }
210 213
211 214 // 3) Tombstones since the last placements_version — gives the
212 - // client the "this row is gone" signal.
215 + // client the "this row is gone" signal.
213 216 $tomb_rows = $wpdb->get_results(
214 217 $wpdb->prepare(
215 218 "SELECT kind, ref_id FROM {$tables['tombstones']} WHERE removed_at_ms > %d ORDER BY removed_at_ms ASC LIMIT %d",
216 219 $placements_version,
@@ -217,9 +220,12 @@
217 220 $cap
218 221 ),
219 222 ARRAY_A
220 223 );
221 - $removed = array( 'placements' => array(), 'folders' => array() );
224 + $removed = array(
225 + 'placements' => array(),
226 + 'folders' => array(),
227 + );
222 228 foreach ( (array) $tomb_rows as $row ) {
223 229 if ( 'folder' === $row['kind'] ) {
224 230 $removed['folders'][] = (int) $row['ref_id'];
225 231 } else {
@@ -227,14 +233,14 @@
227 233 }
228 234 }
229 235
230 236 // 4) Soft-trash events. Tombstones only fire on hard delete, so
231 - // a trashed placement / folder would otherwise stay in the
232 - // client store between F5s. Surface every row whose
233 - // `trashed_at_ms` is fresher than the client's high-water
234 - // mark as a `removed.*` entry. Restoring (clearing
235 - // `trashed_at_ms`) bumps `updated_at_ms` and the row will
236 - // flow back through `placements` / `folders` upserts above.
237 + // a trashed placement / folder would otherwise stay in the
238 + // client store between F5s. Surface every row whose
239 + // `trashed_at_ms` is fresher than the client's high-water
240 + // mark as a `removed.*` entry. Restoring (clearing
241 + // `trashed_at_ms`) bumps `updated_at_ms` and the row will
242 + // flow back through `placements` / `folders` upserts above.
237 243 $trashed_placements = $wpdb->get_col(
238 244 $wpdb->prepare(
239 245 "SELECT id FROM {$tables['placements']}
240 246 WHERE trashed_at_ms IS NOT NULL
@@ -263,26 +269,26 @@
263 269 $removed['folders'][] = (int) $id;
264 270 }
265 271
266 272 // 5) Pending share invites for this viewer (across every folder
267 - // they're invited to). Owner-side share-status changes flow
268 - // through the folder upserts above; this channel is for the
269 - // recipient's "you've been invited" placeholder UI.
273 + // they're invited to). Owner-side share-status changes flow
274 + // through the folder upserts above; this channel is for the
275 + // recipient's "you've been invited" placeholder UI.
270 276 $shares = array();
271 - $sharing_enabled = function_exists( 'desktop_mode_files_sharing_enabled_for' )
272 - ? desktop_mode_files_sharing_enabled_for( $user_id )
277 + $sharing_enabled = function_exists( 'openstation_files_sharing_enabled_for' )
278 + ? openstation_files_sharing_enabled_for( $user_id )
273 279 : true;
274 - if ( $sharing_enabled && function_exists( 'desktop_mode_files_get_pending_shares_for_user' ) ) {
275 - $pending = desktop_mode_files_get_pending_shares_for_user( $user_id, $shares_version );
280 + if ( $sharing_enabled && function_exists( 'openstation_files_get_pending_shares_for_user' ) ) {
281 + $pending = openstation_files_get_pending_shares_for_user( $user_id, $shares_version );
276 282 foreach ( $pending as $row ) {
277 - $shape = desktop_mode_files_shape_share( $row );
278 - $folder = desktop_mode_files_get_folder( $row['folder_id'] );
283 + $shape = openstation_files_shape_share( $row );
284 + $folder = openstation_files_get_folder( $row['folder_id'] );
279 285 if ( $folder ) {
280 - $shape['folderName'] = (string) $folder['name'];
281 - $shape['ownerId'] = (int) $folder['owner_id'];
282 - $owner_user = get_userdata( (int) $folder['owner_id'] );
283 - $shape['ownerName'] = $owner_user ? $owner_user->display_name : '';
284 - $shape['ownerAvatar'] = $owner_user ? get_avatar_url( $owner_user->ID, array( 'size' => 48 ) ) : '';
286 + $shape['folderName'] = (string) $folder['name'];
287 + $shape['ownerId'] = (int) $folder['owner_id'];
288 + $owner_user = get_userdata( (int) $folder['owner_id'] );
289 + $shape['ownerName'] = $owner_user ? $owner_user->display_name : '';
290 + $shape['ownerAvatar'] = $owner_user ? get_avatar_url( $owner_user->ID, array( 'size' => 48 ) ) : '';
285 291 }
286 292 $shares[] = $shape;
287 293 if ( count( $shares ) >= $cap ) {
288 294 $truncated = true;
@@ -293,12 +299,12 @@
293 299 // Pending FILE-share invites ride the same channel. Shapes carry
294 300 // `targetType: 'file'` + `fileId` / `fileName` so the client
295 301 // invite banner can branch (folder shapes have no targetType and
296 302 // default to folder handling).
297 - if ( $sharing_enabled && ! $truncated && function_exists( 'desktop_mode_files_get_pending_file_shares_for_user' ) ) {
298 - $pending_files = desktop_mode_files_get_pending_file_shares_for_user( $user_id, $shares_version );
303 + if ( $sharing_enabled && ! $truncated && function_exists( 'openstation_files_get_pending_file_shares_for_user' ) ) {
304 + $pending_files = openstation_files_get_pending_file_shares_for_user( $user_id, $shares_version );
299 305 foreach ( $pending_files as $row ) {
300 - $shares[] = desktop_mode_files_shape_file_share( $row );
306 + $shares[] = openstation_files_shape_file_share( $row );
301 307 if ( count( $shares ) >= $cap ) {
302 308 $truncated = true;
303 309 break;
304 310 }
@@ -316,17 +322,19 @@
316 322 // restored but any tombstones written in error during the trash
317 323 // path stay in the table). Cleaning them up server-side prevents
318 324 // the same client-side glitch on every subsequent tick.
319 325 $upsert_placement_ids = array_map(
320 - static function ( $p ) { return (int) $p['id']; },
326 + static function ( $p ) {
327 + return (int) $p['id']; },
321 328 $placement_upserts
322 329 );
323 - $upsert_folder_ids = array_map(
324 - static function ( $f ) { return (int) $f['id']; },
330 + $upsert_folder_ids = array_map(
331 + static function ( $f ) {
332 + return (int) $f['id']; },
325 333 $folder_upserts
326 334 );
327 335 if ( ! empty( $upsert_placement_ids ) ) {
328 - $alive_placements = array_flip( $upsert_placement_ids );
336 + $alive_placements = array_flip( $upsert_placement_ids );
329 337 $removed['placements'] = array_values(
330 338 array_filter(
331 339 $removed['placements'],
332 340 static function ( $id ) use ( $alive_placements ) {
@@ -336,12 +344,12 @@
336 344 );
337 345 // Cleanup: drop any tombstones referring to placement ids
338 346 // that are demonstrably alive in this tick. Bounded by the
339 347 // upsert set so the work is per-tick, not table-wide.
340 - desktop_mode_files_purge_stale_tombstones( 'placement', $upsert_placement_ids );
348 + openstation_files_purge_stale_tombstones( 'placement', $upsert_placement_ids );
341 349 }
342 350 if ( ! empty( $upsert_folder_ids ) ) {
343 - $alive_folders = array_flip( $upsert_folder_ids );
351 + $alive_folders = array_flip( $upsert_folder_ids );
344 352 $removed['folders'] = array_values(
345 353 array_filter(
346 354 $removed['folders'],
347 355 static function ( $id ) use ( $alive_folders ) {
@@ -348,9 +356,9 @@
348 356 return ! isset( $alive_folders[ (int) $id ] );
349 357 }
350 358 )
351 359 );
352 - desktop_mode_files_purge_stale_tombstones( 'folder', $upsert_folder_ids );
360 + openstation_files_purge_stale_tombstones( 'folder', $upsert_folder_ids );
353 361 }
354 362
355 363 return array(
356 364 'placements' => $placement_upserts,
@@ -358,9 +366,9 @@
358 366 'removed' => $removed,
359 367 'shares' => array(
360 368 'pending' => $shares,
361 369 ),
362 - 'serverTimeMs' => desktop_mode_files_now_ms(),
370 + 'serverTimeMs' => openstation_files_now_ms(),
363 371 'truncated' => $truncated,
364 372 );
365 373 }
366 374
@@ -373,14 +381,14 @@
373 381 *
374 382 * @param string $kind 'placement' | 'folder'.
375 383 * @param int[] $ids Ids known to be alive in the current tick.
376 384 */
377 -function desktop_mode_files_purge_stale_tombstones( $kind, $ids ) {
385 +function openstation_files_purge_stale_tombstones( $kind, $ids ) {
378 386 if ( empty( $ids ) ) {
379 387 return;
380 388 }
381 389 global $wpdb;
382 - $tables = desktop_mode_files_table_names();
390 + $tables = openstation_files_table_names();
383 391 $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
384 392 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
385 393 $wpdb->query(
386 394 $wpdb->prepare(