PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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
← All changes | includes/content-changes.php +193 -141 0.9.81.1.10 View file →
@@ -1,12 +1,12 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — generic content-change realtime layer.
3 + * OpenStation — generic content-change realtime layer.
4 4 *
5 5 * Records create/update/trash mutations of posts, pages, CPTs,
6 6 * comments, and WooCommerce orders into a per-request changelog and
7 7 * relays them to the parent shell as cross-window broadcasts
8 - * (`desktop-mode.<type>.changed`, payload `{ source, action, ids }`),
8 + * (`os.<type>.changed`, payload `{ source, action, ids }`),
9 9 * so every window listing that content type can refresh without an F5.
10 10 *
11 11 * Three delivery paths, mirroring the Recycle Bin realtime layer
12 12 * (`includes/recycle-bin/realtime.php`) which delegates its own
@@ -13,9 +13,9 @@
13 13 * changelog into this module:
14 14 *
15 15 * 1. **Chromeless footer — fast path.** At `admin_footer` on a
16 16 * chromeless render, one inline script postMessages a
17 - * `desktop-mode-broadcast` envelope per `[type][action]` entry
17 + * `os-broadcast` envelope per `[type][action]` entry
18 18 * to the parent shell. Because the dominant admin mutation flow
19 19 * is form-POST → 302 → GET (the mutating request renders no
20 20 * footer), the changelog is buffered across the redirect in a
21 21 * short-TTL per-user transient and flushed on the next
@@ -32,29 +32,36 @@
32 32 * "entries newer than your seen ts" for opted-in shells. Covers
33 33 * Quick Edit, AJAX status flips, other browser tabs, REST and
34 34 * WP-CLI mutations within one tick (15–60 s).
35 35 *
36 - * @package WPDesktopMode
36 + * @package OpenStation
37 37 */
38 38
39 39 defined( 'ABSPATH' ) || exit;
40 40
41 -const DESKTOP_MODE_CONTENT_CHANGES_LOG_OPTION = '_desktop_mode_content_changes_log';
41 +/**
42 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
43 + * persisted or externally-visible identifier, so renaming it would
44 + * orphan data already written by live installs (or break a live
45 + * URL). The mismatch between this constant's name and its value is
46 + * deliberate — it is NOT a half-finished rename.
47 + */
48 +const OPENSTATION_CONTENT_CHANGES_LOG_OPTION = '_desktop_mode_content_changes_log';
42 49
43 50 /**
44 51 * Milliseconds of heartbeat-changelog history to retain.
45 52 */
46 -const DESKTOP_MODE_CONTENT_CHANGES_LOG_WINDOW_MS = 300000;
53 +const OPENSTATION_CONTENT_CHANGES_LOG_WINDOW_MS = 300000;
47 54
48 55 /**
49 56 * Maximum retained heartbeat-changelog entries.
50 57 */
51 -const DESKTOP_MODE_CONTENT_CHANGES_LOG_MAX = 100;
58 +const OPENSTATION_CONTENT_CHANGES_LOG_MAX = 100;
52 59
53 60 /**
54 61 * TTL for the redirect-surviving per-user changelog buffer.
55 62 */
56 -const DESKTOP_MODE_CONTENT_CHANGES_BUFFER_TTL = 60;
63 +const OPENSTATION_CONTENT_CHANGES_BUFFER_TTL = 60;
57 64
58 65 /**
59 66 * Per-request state shared by the recorder, the footer emitter, and
60 67 * the shutdown handler.
@@ -70,9 +77,9 @@
70 77 * shutdown handler doesn't buffer it a second time.
71 78 *
72 79 * @return array Per-request state, by reference.
73 80 */
74 -function &desktop_mode_content_changes_state() {
81 +function &openstation_content_changes_state() {
75 82 static $state = null;
76 83 if ( null === $state ) {
77 84 $state = array(
78 85 'log' => array(),
@@ -88,10 +95,10 @@
88 95 * Resets the per-request state. Test isolation only.
89 96 *
90 97 * @internal
91 98 */
92 -function desktop_mode_content_changes_reset() {
93 - $state = &desktop_mode_content_changes_state();
99 +function openstation_content_changes_reset() {
100 + $state = &openstation_content_changes_state();
94 101 $state = array(
95 102 'log' => array(),
96 103 'seen' => array(),
97 104 'staged' => array(),
@@ -105,9 +112,9 @@
105 112 * This is the public entry point for third-party plugins with their
106 113 * own storage (an HPOS-style custom table, a settings screen, …):
107 114 * call it from your mutation path and every open window listing your
108 115 * type refreshes, exactly like core content. Pair it with a
109 - * `desktop_mode_soft_reload_rules` filter entry if your list screen
116 + * `openstation_soft_reload_rules` filter entry if your list screen
110 117 * is not a standard `edit.php?post_type=<type>` page.
111 118 *
112 119 * Dedupe is first-writer-wins per `type:id` within the request: core
113 120 * fires the trash verbs (`wp_trash_post`, `untrash_post`) BEFORE the
@@ -117,15 +124,15 @@
117 124 * double-fires (post hooks + `woocommerce_update_order`).
118 125 *
119 126 * @param string $type Content type slug — a post type, `comment`,
120 127 * or `shop_order`. Becomes the broadcast topic
121 - * `desktop-mode.<type>.changed`.
128 + * `os.<type>.changed`.
122 129 * @param int $id Mutated object id.
123 130 * @param string $action One of 'created', 'updated', 'trashed',
124 131 * 'untrashed', 'deleted'.
125 132 * @return bool Whether the change was recorded.
126 133 */
127 -function desktop_mode_content_changes_record( $type, $id, $action ) {
134 +function openstation_content_changes_record( $type, $id, $action ) {
128 135 $type = (string) $type;
129 136 $id = (int) $id;
130 137 $action = (string) $action;
131 138
@@ -146,13 +153,13 @@
146 153 * @param string $type Content type slug.
147 154 * @param int $id Mutated object id.
148 155 * @param string $action Verb (created/updated/trashed/untrashed/deleted).
149 156 */
150 - if ( ! apply_filters( 'desktop_mode_content_changes_should_record', true, $type, $id, $action ) ) {
157 + if ( ! apply_filters( 'openstation_content_changes_should_record', true, $type, $id, $action ) ) {
151 158 return false;
152 159 }
153 160
154 - $state = &desktop_mode_content_changes_state();
161 + $state = &openstation_content_changes_state();
155 162 $key = $type . ':' . $id;
156 163 if ( isset( $state['seen'][ $key ] ) ) {
157 164 return false;
158 165 }
@@ -182,9 +189,9 @@
182 189 * @param string $type Content type slug.
183 190 * @param int $id Mutated object id.
184 191 * @param string $action Verb (created/updated/trashed/untrashed/deleted).
185 192 */
186 - do_action( 'desktop_mode_content_change_recorded', $type, $id, $action );
193 + do_action( 'openstation_content_change_recorded', $type, $id, $action );
187 194
188 195 return true;
189 196 }
190 197
@@ -192,10 +199,10 @@
192 199 * Returns the per-request changelog: `[ type ][ action ] = int[] ids`.
193 200 *
194 201 * @return array
195 202 */
196 -function desktop_mode_content_changes_log() {
197 - $state = &desktop_mode_content_changes_state();
203 +function openstation_content_changes_log() {
204 + $state = &openstation_content_changes_state();
198 205 return $state['log'];
199 206 }
200 207
201 208 /**
@@ -205,12 +212,12 @@
205 212 * @param array $a Base changelog.
206 213 * @param array $b Changelog to merge in.
207 214 * @return array
208 215 */
209 -function desktop_mode_content_changes_merge( $a, $b ) {
216 +function openstation_content_changes_merge( $a, $b ) {
210 217 foreach ( (array) $b as $type => $by_action ) {
211 218 foreach ( (array) $by_action as $action => $ids ) {
212 - $existing = isset( $a[ $type ][ $action ] ) ? (array) $a[ $type ][ $action ] : array();
219 + $existing = isset( $a[ $type ][ $action ] ) ? (array) $a[ $type ][ $action ] : array();
213 220 $a[ $type ][ $action ] = array_values( array_unique( array_merge( $existing, array_map( 'intval', (array) $ids ) ) ) );
214 221 }
215 222 }
216 223 return $a;
@@ -221,10 +228,10 @@
221 228 *
222 229 * @param int $user_id User id.
223 230 * @return string
224 231 */
225 -function desktop_mode_content_changes_buffer_key( $user_id ) {
226 - return 'desktop_mode_content_buf_' . (int) $user_id;
232 +function openstation_content_changes_buffer_key( $user_id ) {
233 + return 'openstation_content_buf_' . (int) $user_id;
227 234 }
228 235
229 236 /**
230 237 * `wp_after_insert_post` handler — posts, pages, and every `show_ui`
@@ -238,9 +245,9 @@
238 245 *
239 246 * Post types without `show_ui` are skipped: they have no list screen
240 247 * to refresh, and internal types (notes, …) run their own realtime.
241 248 * Plugins that want one tracked anyway can call
242 - * `desktop_mode_content_changes_record()` from their own hooks.
249 + * `openstation_content_changes_record()` from their own hooks.
243 250 *
244 251 * @param int $post_id Post id.
245 252 * @param WP_Post $post Saved post.
246 253 * @param bool $update Whether this is an update.
@@ -245,9 +252,9 @@
245 252 * @param WP_Post $post Saved post.
246 253 * @param bool $update Whether this is an update.
247 254 * @param WP_Post|null $post_before Pre-save post, null on creation.
248 255 */
249 -function desktop_mode_content_changes_on_after_insert_post( $post_id, $post, $update, $post_before ) {
256 +function openstation_content_changes_on_after_insert_post( $post_id, $post, $update, $post_before ) {
250 257 if ( ! $post instanceof WP_Post ) {
251 258 return;
252 259 }
253 260 if ( wp_is_post_revision( $post ) || wp_is_post_autosave( $post ) ) {
@@ -267,9 +274,9 @@
267 274 // The first real save of a new post arrives as an "update" of the
268 275 // auto-draft shell `post-new.php` created — report it as created.
269 276 $is_created = ! $update || ( $post_before instanceof WP_Post && 'auto-draft' === $post_before->post_status );
270 277
271 - desktop_mode_content_changes_record( $post->post_type, (int) $post_id, $is_created ? 'created' : 'updated' );
278 + openstation_content_changes_record( $post->post_type, (int) $post_id, $is_created ? 'created' : 'updated' );
272 279 }
273 280
274 281 /**
275 282 * `transition_comment_status` handler.
@@ -282,9 +289,9 @@
282 289 * @param string $new_status New comment status.
283 290 * @param string $old_status Old comment status.
284 291 * @param WP_Comment $comment Comment object.
285 292 */
286 -function desktop_mode_content_changes_on_comment_transition( $new_status, $old_status, $comment ) {
293 +function openstation_content_changes_on_comment_transition( $new_status, $old_status, $comment ) {
287 294 if ( 'trash' === $new_status || 'trash' === $old_status ) {
288 295 return;
289 296 }
290 297 if ( ! $comment instanceof WP_Comment ) {
@@ -289,9 +296,9 @@
289 296 }
290 297 if ( ! $comment instanceof WP_Comment ) {
291 298 return;
292 299 }
293 - desktop_mode_content_changes_record( 'comment', (int) $comment->comment_ID, 'updated' );
300 + openstation_content_changes_record( 'comment', (int) $comment->comment_ID, 'updated' );
294 301 }
295 302
296 303 /**
297 304 * Wires the WooCommerce order hooks. No-op unless WooCommerce is
@@ -301,38 +308,56 @@
301 308 * in custom tables and none of the post hooks fire. Under legacy
302 309 * (posts-table) storage the post hooks fire too; the recorder's
303 310 * per-request dedupe collapses the double-fire. The type is always
304 311 * recorded as `shop_order` so one broadcast topic
305 - * (`desktop-mode.shop_order.changed`) serves both storage modes.
312 + * (`os.shop_order.changed`) serves both storage modes.
306 313 *
307 314 * @return bool Whether the hooks were registered.
308 315 */
309 -function desktop_mode_content_changes_register_wc_hooks() {
316 +function openstation_content_changes_register_wc_hooks() {
310 317 if ( ! class_exists( 'WooCommerce' ) || ! function_exists( 'wc_get_order' ) ) {
311 318 return false;
312 319 }
313 320
314 - add_action( 'woocommerce_new_order', function ( $order_id ) {
315 - desktop_mode_content_changes_record( 'shop_order', (int) $order_id, 'created' );
316 - } );
317 - add_action( 'woocommerce_update_order', function ( $order_id ) {
318 - desktop_mode_content_changes_record( 'shop_order', (int) $order_id, 'updated' );
319 - } );
321 + add_action(
322 + 'woocommerce_new_order',
323 + function ( $order_id ) {
324 + openstation_content_changes_record( 'shop_order', (int) $order_id, 'created' );
325 + }
326 + );
327 + add_action(
328 + 'woocommerce_update_order',
329 + function ( $order_id ) {
330 + openstation_content_changes_record( 'shop_order', (int) $order_id, 'updated' );
331 + }
332 + );
320 333 // Some AJAX status-flip paths reach `woocommerce_order_status_changed`
321 334 // without `woocommerce_update_order`; the dedupe set absorbs the
322 335 // overlap when both fire.
323 - add_action( 'woocommerce_order_status_changed', function ( $order_id ) {
324 - desktop_mode_content_changes_record( 'shop_order', (int) $order_id, 'updated' );
325 - } );
326 - add_action( 'woocommerce_trash_order', function ( $order_id ) {
327 - desktop_mode_content_changes_record( 'shop_order', (int) $order_id, 'trashed' );
328 - } );
329 - add_action( 'woocommerce_untrash_order', function ( $order_id ) {
330 - desktop_mode_content_changes_record( 'shop_order', (int) $order_id, 'untrashed' );
331 - } );
332 - add_action( 'woocommerce_delete_order', function ( $order_id ) {
333 - desktop_mode_content_changes_record( 'shop_order', (int) $order_id, 'deleted' );
334 - } );
336 + add_action(
337 + 'woocommerce_order_status_changed',
338 + function ( $order_id ) {
339 + openstation_content_changes_record( 'shop_order', (int) $order_id, 'updated' );
340 + }
341 + );
342 + add_action(
343 + 'woocommerce_trash_order',
344 + function ( $order_id ) {
345 + openstation_content_changes_record( 'shop_order', (int) $order_id, 'trashed' );
346 + }
347 + );
348 + add_action(
349 + 'woocommerce_untrash_order',
350 + function ( $order_id ) {
351 + openstation_content_changes_record( 'shop_order', (int) $order_id, 'untrashed' );
352 + }
353 + );
354 + add_action(
355 + 'woocommerce_delete_order',
356 + function ( $order_id ) {
357 + openstation_content_changes_record( 'shop_order', (int) $order_id, 'deleted' );
358 + }
359 + );
335 360
336 361 return true;
337 362 }
338 363
@@ -342,29 +367,29 @@
342 367 * Merges the in-memory changelog with the redirect-surviving buffer
343 368 * (consumed on read), builds one broadcast envelope per
344 369 * `[ type ][ action ]`, and prints one inline script that postMessages
345 370 * each to the parent shell. The parent's broadcast receiver fans them
346 - * out as `desktop-mode.<type>.changed` — iframe list pages soft-reload
371 + * out as `os.<type>.changed` — iframe list pages soft-reload
347 372 * and native list windows refetch.
348 373 *
349 374 * Runs at `admin_footer` priority 100, same slot as the Recycle Bin's
350 375 * bin-specific ts signal.
351 376 */
352 -function desktop_mode_content_changes_emit_footer() {
353 - if ( ! function_exists( 'desktop_mode_is_chromeless_request' ) || ! desktop_mode_is_chromeless_request() ) {
377 +function openstation_content_changes_emit_footer() {
378 + if ( ! function_exists( 'openstation_is_chromeless_request' ) || ! openstation_is_chromeless_request() ) {
354 379 return;
355 380 }
356 381
357 - $state = &desktop_mode_content_changes_state();
382 + $state = &openstation_content_changes_state();
358 383 $log = $state['log'];
359 384
360 385 $user_id = get_current_user_id();
361 386 if ( $user_id > 0 ) {
362 - $key = desktop_mode_content_changes_buffer_key( $user_id );
387 + $key = openstation_content_changes_buffer_key( $user_id );
363 388 $buffered = get_transient( $key );
364 389 if ( is_array( $buffered ) && ! empty( $buffered ) ) {
365 390 delete_transient( $key );
366 - $log = desktop_mode_content_changes_merge( $buffered, $log );
391 + $log = openstation_content_changes_merge( $buffered, $log );
367 392 }
368 393 }
369 394
370 395 // The in-memory log is consumed regardless of whether anything is
@@ -381,13 +406,13 @@
381 406 foreach ( $by_action as $action => $ids ) {
382 407 /**
383 408 * Filters the broadcast topic for a content-change type.
384 409 *
385 - * @param string $topic Default `desktop-mode.<type>.changed`.
410 + * @param string $topic Default `os.<type>.changed`.
386 411 * @param string $type Content type slug.
387 412 * @param string $action Verb for this envelope.
388 413 */
389 - $topic = (string) apply_filters( 'desktop_mode_content_change_topic', 'desktop-mode.' . $type . '.changed', $type, $action );
414 + $topic = (string) apply_filters( 'openstation_content_change_topic', 'os.' . $type . '.changed', $type, $action );
390 415
391 416 $broadcasts[] = array(
392 417 'topic' => $topic,
393 418 'payload' => array(
@@ -407,9 +432,9 @@
407 432 * Return an empty array to suppress the emit.
408 433 *
409 434 * @param array $broadcasts Broadcast envelopes.
410 435 */
411 - $broadcasts = (array) apply_filters( 'desktop_mode_content_changes_broadcasts', $broadcasts );
436 + $broadcasts = (array) apply_filters( 'openstation_content_changes_broadcasts', $broadcasts );
412 437 if ( empty( $broadcasts ) ) {
413 438 return;
414 439 }
415 440
@@ -418,9 +443,9 @@
418 443 return;
419 444 }
420 445
421 446 ?>
422 - <script id="desktop-mode-content-changes-signal">
447 + <script id="os-content-changes-signal">
423 448 ( function () {
424 449 if ( window.parent === window ) {
425 450 return;
426 451 }
@@ -428,9 +453,9 @@
428 453 var broadcasts = <?php echo $broadcasts_json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode output. ?>;
429 454 for ( var i = 0; i < broadcasts.length; i++ ) {
430 455 try {
431 456 window.parent.postMessage( {
432 - type: 'desktop-mode-broadcast',
457 + type: 'os-broadcast',
433 458 topic: broadcasts[ i ].topic,
434 459 payload: broadcasts[ i ].payload
435 460 }, origin );
436 461 } catch ( _err ) { /* parent gone */ }
@@ -444,9 +469,9 @@
444 469 * broadcast envelopes.
445 470 *
446 471 * @param array $broadcasts Emitted broadcast envelopes.
447 472 */
448 - do_action( 'desktop_mode_content_changes_emitted', $broadcasts );
473 + do_action( 'openstation_content_changes_emitted', $broadcasts );
449 474 }
450 475
451 476 /**
452 477 * `shutdown` handler — persists the heartbeat changelog and buffers
@@ -454,10 +479,10 @@
454 479 *
455 480 * One `update_option` per mutating request; requests that recorded
456 481 * nothing pay a static-array read and return.
457 482 */
458 -function desktop_mode_content_changes_on_shutdown() {
459 - $state = &desktop_mode_content_changes_state();
483 +function openstation_content_changes_on_shutdown() {
484 + $state = &openstation_content_changes_state();
460 485
461 486 if ( ! empty( $state['staged'] ) ) {
462 487 $now = (int) round( microtime( true ) * 1000 );
463 488
@@ -466,9 +491,9 @@
466 491 foreach ( $state['staged'] as $row ) {
467 492 $grouped[ $row['type'] . '|' . $row['action'] ][] = (int) $row['id'];
468 493 }
469 494
470 - $log = get_option( DESKTOP_MODE_CONTENT_CHANGES_LOG_OPTION, array() );
495 + $log = get_option( OPENSTATION_CONTENT_CHANGES_LOG_OPTION, array() );
471 496 $entries = ( is_array( $log ) && isset( $log['entries'] ) && is_array( $log['entries'] ) ) ? $log['entries'] : array();
472 497
473 498 foreach ( $grouped as $group_key => $ids ) {
474 499 list( $type, $action ) = explode( '|', $group_key, 2 );
@@ -481,18 +506,23 @@
481 506 }
482 507
483 508 // Prune: drop entries older than the retention window, cap the
484 509 // tail. The option stays small no matter how chatty the site.
485 - $cutoff = $now - DESKTOP_MODE_CONTENT_CHANGES_LOG_WINDOW_MS;
486 - $entries = array_values( array_filter( $entries, function ( $entry ) use ( $cutoff ) {
487 - return isset( $entry['ts'] ) && (int) $entry['ts'] >= $cutoff;
488 - } ) );
489 - if ( count( $entries ) > DESKTOP_MODE_CONTENT_CHANGES_LOG_MAX ) {
490 - $entries = array_slice( $entries, -DESKTOP_MODE_CONTENT_CHANGES_LOG_MAX );
510 + $cutoff = $now - OPENSTATION_CONTENT_CHANGES_LOG_WINDOW_MS;
511 + $entries = array_values(
512 + array_filter(
513 + $entries,
514 + function ( $entry ) use ( $cutoff ) {
515 + return isset( $entry['ts'] ) && (int) $entry['ts'] >= $cutoff;
516 + }
517 + )
518 + );
519 + if ( count( $entries ) > OPENSTATION_CONTENT_CHANGES_LOG_MAX ) {
520 + $entries = array_slice( $entries, -OPENSTATION_CONTENT_CHANGES_LOG_MAX );
491 521 }
492 522
493 523 update_option(
494 - DESKTOP_MODE_CONTENT_CHANGES_LOG_OPTION,
524 + OPENSTATION_CONTENT_CHANGES_LOG_OPTION,
495 525 array(
496 526 'ts' => $now,
497 527 'entries' => $entries,
498 528 ),
@@ -507,12 +537,12 @@
507 537 if ( $user_id <= 0 ) {
508 538 return;
509 539 }
510 540
511 - $key = desktop_mode_content_changes_buffer_key( $user_id );
541 + $key = openstation_content_changes_buffer_key( $user_id );
512 542 $existing = get_transient( $key );
513 - $merged = desktop_mode_content_changes_merge( is_array( $existing ) ? $existing : array(), $state['log'] );
514 - set_transient( $key, $merged, DESKTOP_MODE_CONTENT_CHANGES_BUFFER_TTL );
543 + $merged = openstation_content_changes_merge( is_array( $existing ) ? $existing : array(), $state['log'] );
544 + set_transient( $key, $merged, OPENSTATION_CONTENT_CHANGES_BUFFER_TTL );
515 545 }
516 546
517 547 /**
518 548 * Heartbeat handler — answers "which content changed since you last
@@ -517,28 +547,28 @@
517 547 /**
518 548 * Heartbeat handler — answers "which content changed since you last
519 549 * heard from me?".
520 550 *
521 - * Opt-in via the client-sent `desktop_mode_content_changes_seen_ts`
551 + * Opt-in via the client-sent `openstation_content_changes_seen_ts`
522 552 * key; requests without it early-return so non-desktop tabs pay zero
523 553 * per tick. The response carries the server high-water mark plus the
524 554 * entries newer than the client's seen ts; the shell re-broadcasts
525 - * each as `desktop-mode.<type>.changed`.
555 + * each as `os.<type>.changed`.
526 556 *
527 557 * @param array $response Heartbeat response.
528 558 * @param array $data Client-sent payload.
529 559 * @return array
530 560 */
531 -function desktop_mode_content_changes_heartbeat_received( $response, $data ) {
561 +function openstation_content_changes_heartbeat_received( $response, $data ) {
532 562 if ( ! is_array( $response ) ) {
533 563 $response = array();
534 564 }
535 - if ( ! isset( $data['desktop_mode_content_changes_seen_ts'] ) ) {
565 + if ( ! isset( $data['openstation_content_changes_seen_ts'] ) ) {
536 566 return $response;
537 567 }
538 568
539 - $seen = (int) $data['desktop_mode_content_changes_seen_ts'];
540 - $log = get_option( DESKTOP_MODE_CONTENT_CHANGES_LOG_OPTION, array() );
569 + $seen = (int) $data['openstation_content_changes_seen_ts'];
570 + $log = get_option( OPENSTATION_CONTENT_CHANGES_LOG_OPTION, array() );
541 571
542 572 $ts = ( is_array( $log ) && isset( $log['ts'] ) ) ? (int) $log['ts'] : 0;
543 573 $entries = ( is_array( $log ) && isset( $log['entries'] ) && is_array( $log['entries'] ) ) ? $log['entries'] : array();
544 574
@@ -548,9 +578,9 @@
548 578 $fresh[] = $entry;
549 579 }
550 580 }
551 581
552 - $response['desktop_mode_content_changes'] = array(
582 + $response['openstation_content_changes'] = array(
553 583 'ts' => $ts,
554 584 'entries' => $fresh,
555 585 );
556 586
@@ -559,9 +589,9 @@
559 589
560 590 /**
561 591 * Converts a plugin file path to a stable positive integer suitable
562 592 * for use as the `$id` parameter of
563 - * `desktop_mode_content_changes_record()`.
593 + * `openstation_content_changes_record()`.
564 594 *
565 595 * The record function requires a positive integer for its ID slot (used
566 596 * for per-request deduplication keyed as `type:id`). Plugin files are
567 597 * strings (`akismet/akismet.php`), so we derive a deterministic integer
@@ -571,9 +601,9 @@
571 601 *
572 602 * @param string $plugin_file Plugin file path (relative to plugins dir).
573 603 * @return int Positive integer ID.
574 604 */
575 -function desktop_mode_content_changes_plugin_id( $plugin_file ) {
605 +function openstation_content_changes_plugin_id( $plugin_file ) {
576 606 $hash = abs( crc32( (string) $plugin_file ) );
577 607 return max( 1, $hash );
578 608 }
579 609
@@ -582,63 +612,79 @@
582 612 * and deletions are recorded into the realtime changelog.
583 613 *
584 614 * Every open window listing plugins (native Plugins window Installed tab,
585 615 * classic `plugins.php`) will then refresh via the
586 - * `desktop-mode.plugin.changed` broadcast — the same mechanism
587 - * posts/pages use for `desktop-mode.post.changed`.
616 + * `os.plugin.changed` broadcast — the same mechanism
617 + * posts/pages use for `os.post.changed`.
588 618 */
589 -function desktop_mode_content_changes_register_plugin_hooks() {
590 - add_action( 'activated_plugin', function ( $plugin_file ) {
591 - desktop_mode_content_changes_record(
592 - 'plugin',
593 - desktop_mode_content_changes_plugin_id( $plugin_file ),
594 - 'activated'
595 - );
596 - } );
619 +function openstation_content_changes_register_plugin_hooks() {
620 + add_action(
621 + 'activated_plugin',
622 + function ( $plugin_file ) {
623 + openstation_content_changes_record(
624 + 'plugin',
625 + openstation_content_changes_plugin_id( $plugin_file ),
626 + 'activated'
627 + );
628 + }
629 + );
597 630
598 - add_action( 'deactivated_plugin', function ( $plugin_file ) {
599 - desktop_mode_content_changes_record(
600 - 'plugin',
601 - desktop_mode_content_changes_plugin_id( $plugin_file ),
602 - 'deactivated'
603 - );
604 - } );
605 -
606 - add_action( 'deleted_plugin', function ( $plugin_file, $deleted ) {
607 - if ( $deleted ) {
608 - desktop_mode_content_changes_record(
631 + add_action(
632 + 'deactivated_plugin',
633 + function ( $plugin_file ) {
634 + openstation_content_changes_record(
609 635 'plugin',
610 - desktop_mode_content_changes_plugin_id( $plugin_file ),
611 - 'deleted'
636 + openstation_content_changes_plugin_id( $plugin_file ),
637 + 'deactivated'
612 638 );
613 639 }
614 - }, 10, 2 );
640 + );
615 641
642 + add_action(
643 + 'deleted_plugin',
644 + function ( $plugin_file, $deleted ) {
645 + if ( $deleted ) {
646 + openstation_content_changes_record(
647 + 'plugin',
648 + openstation_content_changes_plugin_id( $plugin_file ),
649 + 'deleted'
650 + );
651 + }
652 + },
653 + 10,
654 + 2
655 + );
656 +
616 657 // `upgrader_process_complete` covers installs from wp-admin/plugin-install.php
617 658 // (AJAX path, no page navigation) and bulk installs from update.php.
618 - add_action( 'upgrader_process_complete', function ( $upgrader, $options ) {
619 - if (
659 + add_action(
660 + 'upgrader_process_complete',
661 + function ( $upgrader, $options ) {
662 + if (
620 663 ! isset( $options['type'], $options['action'] ) ||
621 664 'plugin' !== $options['type'] ||
622 665 'install' !== $options['action']
623 - ) {
624 - return;
625 - }
626 - $plugins = ! empty( $options['plugins'] ) ? (array) $options['plugins'] : array();
627 - if ( empty( $plugins ) && is_callable( array( $upgrader, 'plugin_info' ) ) ) {
628 - $info = $upgrader->plugin_info();
629 - if ( $info ) {
630 - $plugins = array( $info );
666 + ) {
667 + return;
631 668 }
632 - }
633 - foreach ( $plugins as $plugin_file ) {
634 - desktop_mode_content_changes_record(
635 - 'plugin',
636 - desktop_mode_content_changes_plugin_id( (string) $plugin_file ),
637 - 'installed'
638 - );
639 - }
640 - }, 10, 2 );
669 + $plugins = ! empty( $options['plugins'] ) ? (array) $options['plugins'] : array();
670 + if ( empty( $plugins ) && is_callable( array( $upgrader, 'plugin_info' ) ) ) {
671 + $info = $upgrader->plugin_info();
672 + if ( $info ) {
673 + $plugins = array( $info );
674 + }
675 + }
676 + foreach ( $plugins as $plugin_file ) {
677 + openstation_content_changes_record(
678 + 'plugin',
679 + openstation_content_changes_plugin_id( (string) $plugin_file ),
680 + 'installed'
681 + );
682 + }
683 + },
684 + 10,
685 + 2
686 + );
641 687 }
642 688
643 689 /**
644 690 * Wires every content-change hook.
@@ -643,25 +689,31 @@
643 689 /**
644 690 * Wires every content-change hook.
645 691 *
646 692 * One bootstrap so the wiring is auditable —
647 - * `grep desktop_mode_content_changes_record` finds every emitter.
693 + * `grep openstation_content_changes_record` finds every emitter.
648 694 */
649 -function desktop_mode_content_changes_register_hooks() {
650 - add_action( 'wp_after_insert_post', 'desktop_mode_content_changes_on_after_insert_post', 10, 4 );
695 +function openstation_content_changes_register_hooks() {
696 + add_action( 'wp_after_insert_post', 'openstation_content_changes_on_after_insert_post', 10, 4 );
651 697
652 - add_action( 'wp_insert_comment', function ( $comment_id ) {
653 - desktop_mode_content_changes_record( 'comment', (int) $comment_id, 'created' );
654 - } );
655 - add_action( 'edit_comment', function ( $comment_id ) {
656 - desktop_mode_content_changes_record( 'comment', (int) $comment_id, 'updated' );
657 - } );
658 - add_action( 'transition_comment_status', 'desktop_mode_content_changes_on_comment_transition', 10, 3 );
698 + add_action(
699 + 'wp_insert_comment',
700 + function ( $comment_id ) {
701 + openstation_content_changes_record( 'comment', (int) $comment_id, 'created' );
702 + }
703 + );
704 + add_action(
705 + 'edit_comment',
706 + function ( $comment_id ) {
707 + openstation_content_changes_record( 'comment', (int) $comment_id, 'updated' );
708 + }
709 + );
710 + add_action( 'transition_comment_status', 'openstation_content_changes_on_comment_transition', 10, 3 );
659 711
660 - desktop_mode_content_changes_register_wc_hooks();
661 - desktop_mode_content_changes_register_plugin_hooks();
712 + openstation_content_changes_register_wc_hooks();
713 + openstation_content_changes_register_plugin_hooks();
662 714
663 - add_action( 'admin_footer', 'desktop_mode_content_changes_emit_footer', 100 );
664 - add_action( 'shutdown', 'desktop_mode_content_changes_on_shutdown' );
665 - add_filter( 'heartbeat_received', 'desktop_mode_content_changes_heartbeat_received', 10, 2 );
715 + add_action( 'admin_footer', 'openstation_content_changes_emit_footer', 100 );
716 + add_action( 'shutdown', 'openstation_content_changes_on_shutdown' );
717 + add_filter( 'heartbeat_received', 'openstation_content_changes_heartbeat_received', 10, 2 );
666 718 }
667 -add_action( 'init', 'desktop_mode_content_changes_register_hooks', 5 );
719 +add_action( 'init', 'openstation_content_changes_register_hooks', 5 );