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-graph/graph-builder.php +52 -45 0.9.81.1.10 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Content Graph: graph builder.
3 + * OpenStation — Content Graph: graph builder.
4 4 *
5 5 * Walks the WordPress post store to produce two arrays for the bundle:
6 6 *
7 7 * - `nodes` — one entry per post matching the requested types.
@@ -19,9 +19,9 @@
19 19 * also explicitly bust the cache on `save_post` and `deleted_post` so
20 20 * editors don't see stale data even if some other process pre-warms
21 21 * the transient.
22 22 *
23 - * @package WPDesktopMode
23 + * @package OpenStation
24 24 */
25 25
26 26 defined( 'ABSPATH' ) || exit;
27 27
@@ -29,10 +29,17 @@
29 29 // transients still alive at upgrade time would otherwise return the
30 30 // previous schema (e.g. missing per-node `contributor_ids`) and
31 31 // surface as runtime errors on the client. Each bump is a one-time
32 32 // cache miss for every site that updates the plugin.
33 -const DESKTOP_MODE_CONTENT_GRAPH_TRANSIENT_PREFIX = 'desktop_mode_cg3_';
34 -const DESKTOP_MODE_CONTENT_GRAPH_TRANSIENT_TTL = 6 * HOUR_IN_SECONDS;
33 +/**
34 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
35 + * persisted or externally-visible identifier, so renaming it would
36 + * orphan data already written by live installs (or break a live
37 + * URL). The mismatch between this constant's name and its value is
38 + * deliberate — it is NOT a half-finished rename.
39 + */
40 +const OPENSTATION_CONTENT_GRAPH_TRANSIENT_PREFIX = 'desktop_mode_cg3_';
41 +const OPENSTATION_CONTENT_GRAPH_TRANSIENT_TTL = 6 * HOUR_IN_SECONDS;
35 42
36 43 /**
37 44 * Build (or reuse the cached version of) the graph payload for the
38 45 * requested post types.
@@ -55,10 +62,10 @@
55 62 * },
56 63 * stats: array{ nodes: int, edges: int, generated_at: int }
57 64 * }
58 65 */
59 -function desktop_mode_content_graph_build( array $types ) {
60 - $types = desktop_mode_content_graph_normalize_types( $types );
66 +function openstation_content_graph_build( array $types ) {
67 + $types = openstation_content_graph_normalize_types( $types );
61 68 if ( empty( $types ) ) {
62 69 return array(
63 70 'nodes' => array(),
64 71 'edges' => array(),
@@ -74,15 +81,15 @@
74 81 ),
75 82 );
76 83 }
77 84
78 - $cache_key = desktop_mode_content_graph_cache_key( $types );
85 + $cache_key = openstation_content_graph_cache_key( $types );
79 86 $cached = get_transient( $cache_key );
80 87 if ( is_array( $cached ) && isset( $cached['nodes'], $cached['edges'] ) ) {
81 88 return $cached;
82 89 }
83 90
84 - $rows = desktop_mode_content_graph_fetch_rows( $types );
91 + $rows = openstation_content_graph_fetch_rows( $types );
85 92
86 93 $post_ids = array();
87 94 foreach ( $rows as $row ) {
88 95 $post_ids[] = (int) $row->ID;
@@ -87,14 +94,14 @@
87 94 foreach ( $rows as $row ) {
88 95 $post_ids[] = (int) $row->ID;
89 96 }
90 97
91 - $terms_by_post = desktop_mode_content_graph_collect_post_terms( $post_ids );
98 + $terms_by_post = openstation_content_graph_collect_post_terms( $post_ids );
92 99 // Distinct revision authors per post — used to pull collaborator
93 100 // posts toward both the primary author's cluster AND the
94 101 // contributor's clusters when grouping by author. Bulk-queried so
95 102 // we don't N+1 `wp_get_post_revisions` per node.
96 - $contribs_by_post = desktop_mode_content_graph_collect_post_contributors( $post_ids );
103 + $contribs_by_post = openstation_content_graph_collect_post_contributors( $post_ids );
97 104
98 105 $default_category = max( 1, (int) get_option( 'default_category', 1 ) );
99 106
100 107 $nodes = array();
@@ -102,12 +109,12 @@
102 109 $author_ids = array();
103 110 $cat_ids = array();
104 111 $tag_ids = array();
105 112 foreach ( $rows as $row ) {
106 - $id = (int) $row->ID;
107 - $author_id = (int) $row->post_author;
108 - $year = 0;
109 - $year_month = '';
113 + $id = (int) $row->ID;
114 + $author_id = (int) $row->post_author;
115 + $year = 0;
116 + $year_month = '';
110 117 if ( ! empty( $row->post_date ) ) {
111 118 // Use post_date (site-local) rather than post_date_gmt for the
112 119 // "year published" / "year-month published" buckets — editors
113 120 // think in their own timezone.
@@ -129,9 +136,9 @@
129 136 }
130 137 $post_tags = isset( $terms_by_post[ $id ]['post_tag'] )
131 138 ? $terms_by_post[ $id ]['post_tag']
132 139 : array();
133 - $contribs = isset( $contribs_by_post[ $id ] )
140 + $contribs = isset( $contribs_by_post[ $id ] )
134 141 ? $contribs_by_post[ $id ]
135 142 : array();
136 143 // Strip the primary author from the contributor list — the
137 144 // node carries that separately in `author_id`, and surfacing
@@ -147,9 +154,9 @@
147 154 )
148 155 );
149 156 }
150 157
151 - $node = array(
158 + $node = array(
152 159 'id' => $id,
153 160 'type' => (string) $row->post_type,
154 161 'title' => (string) get_the_title( $row ),
155 162 'status' => (string) $row->post_status,
@@ -180,11 +187,11 @@
180 187 }
181 188 }
182 189
183 190 $groups = array(
184 - 'authors' => desktop_mode_content_graph_format_author_catalog( array_keys( $author_ids ) ),
185 - 'categories' => desktop_mode_content_graph_format_term_catalog( array_keys( $cat_ids ), 'category' ),
186 - 'tags' => desktop_mode_content_graph_format_term_catalog( array_keys( $tag_ids ), 'post_tag' ),
191 + 'authors' => openstation_content_graph_format_author_catalog( array_keys( $author_ids ) ),
192 + 'categories' => openstation_content_graph_format_term_catalog( array_keys( $cat_ids ), 'category' ),
193 + 'tags' => openstation_content_graph_format_term_catalog( array_keys( $tag_ids ), 'post_tag' ),
187 194 );
188 195
189 196 $edges_seen = array();
190 197 $edges = array();
@@ -189,9 +196,9 @@
189 196 $edges_seen = array();
190 197 $edges = array();
191 198 foreach ( $rows as $row ) {
192 199 $from = (int) $row->ID;
193 - $tos = desktop_mode_content_graph_extract_internal_links( (string) $row->post_content );
200 + $tos = openstation_content_graph_extract_internal_links( (string) $row->post_content );
194 201 foreach ( $tos as $to ) {
195 202 if ( $to === $from ) {
196 203 continue;
197 204 }
@@ -223,9 +230,9 @@
223 230 'generated_at' => time(),
224 231 ),
225 232 );
226 233
227 - set_transient( $cache_key, $payload, DESKTOP_MODE_CONTENT_GRAPH_TRANSIENT_TTL );
234 + set_transient( $cache_key, $payload, OPENSTATION_CONTENT_GRAPH_TRANSIENT_TTL );
228 235
229 236 return $payload;
230 237 }
231 238
@@ -232,16 +239,16 @@
232 239 /**
233 240 * Filter, sanitize, and uniquify the requested type slugs against the
234 241 * public post-type registry. Returned slugs are guaranteed to exist
235 242 * AND to be among the slugs declared by
236 - * `desktop_mode_content_graph_post_types()`.
243 + * `openstation_content_graph_post_types()`.
237 244 *
238 245 * @param string[] $types
239 246 * @return string[]
240 247 */
241 -function desktop_mode_content_graph_normalize_types( array $types ) {
248 +function openstation_content_graph_normalize_types( array $types ) {
242 249 $allowed = array();
243 - foreach ( desktop_mode_content_graph_post_types() as $entry ) {
250 + foreach ( openstation_content_graph_post_types() as $entry ) {
244 251 if ( ! empty( $entry['slug'] ) ) {
245 252 $allowed[ (string) $entry['slug'] ] = true;
246 253 }
247 254 }
@@ -272,9 +279,9 @@
272 279 *
273 280 * @param string[] $types Already normalized.
274 281 * @return array{ where: string, values: array, key: string }
275 282 */
276 -function desktop_mode_content_graph_visibility_sql( array $types ) {
283 +function openstation_content_graph_visibility_sql( array $types ) {
277 284 $placeholders = implode( ',', array_fill( 0, count( $types ), '%s' ) );
278 285 $values = $types;
279 286
280 287 $priv_types = array();
@@ -322,11 +329,11 @@
322 329 *
323 330 * @param string[] $types Already normalized.
324 331 * @return string
325 332 */
326 -function desktop_mode_content_graph_cache_key( array $types ) {
333 +function openstation_content_graph_cache_key( array $types ) {
327 334 global $wpdb;
328 - $visibility = desktop_mode_content_graph_visibility_sql( $types );
335 + $visibility = openstation_content_graph_visibility_sql( $types );
329 336 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
330 337 $hash = (string) $wpdb->get_var(
331 338 $wpdb->prepare(
332 339 "SELECT MD5( GROUP_CONCAT( CONCAT( ID, ':', post_modified_gmt ) ORDER BY ID ) )
@@ -338,9 +345,9 @@
338 345 // phpcs:enable
339 346 if ( '' === $hash || null === $hash ) {
340 347 $hash = 'empty';
341 348 }
342 - return DESKTOP_MODE_CONTENT_GRAPH_TRANSIENT_PREFIX . substr( md5( implode( ',', $types ) . '|' . $visibility['key'] . '|' . $hash ), 0, 24 );
349 + return OPENSTATION_CONTENT_GRAPH_TRANSIENT_PREFIX . substr( md5( implode( ',', $types ) . '|' . $visibility['key'] . '|' . $hash ), 0, 24 );
343 350 }
344 351
345 352 /**
346 353 * Fetch the participating posts in a single query. Returns full rows
@@ -349,16 +356,16 @@
349 356 *
350 357 * Rows are scoped to what the current user can read: published posts,
351 358 * plus private posts only where the user holds the type's
352 359 * `read_private_posts` capability (or authored the post). See
353 - * `desktop_mode_content_graph_visibility_sql()`.
360 + * `openstation_content_graph_visibility_sql()`.
354 361 *
355 362 * @param string[] $types Already normalized.
356 363 * @return WP_Post[]
357 364 */
358 -function desktop_mode_content_graph_fetch_rows( array $types ) {
365 +function openstation_content_graph_fetch_rows( array $types ) {
359 366 global $wpdb;
360 - $visibility = desktop_mode_content_graph_visibility_sql( $types );
367 + $visibility = openstation_content_graph_visibility_sql( $types );
361 368 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
362 369 $rows = $wpdb->get_results(
363 370 $wpdb->prepare(
364 371 "SELECT ID, post_type, post_status, post_title, post_name, post_content, post_author, post_date
@@ -395,9 +402,9 @@
395 402 *
396 403 * @param string $content
397 404 * @return int[] Unique target post ids (order preserved).
398 405 */
399 -function desktop_mode_content_graph_extract_internal_links( $content ) {
406 +function openstation_content_graph_extract_internal_links( $content ) {
400 407 if ( '' === trim( (string) $content ) ) {
401 408 return array();
402 409 }
403 410
@@ -441,13 +448,13 @@
441 448 }
442 449
443 450 /**
444 451 * Cache invalidation. Any post-type change wipes every transient
445 - * carrying the `desktop_mode_cg_` prefix. We don't have a per-type
452 + * carrying the `openstation_cg_` prefix. We don't have a per-type
446 453 * index so we wipe globally, the cost is one extra build on next
447 454 * open which dominates the time-savings on subsequent opens.
448 455 */
449 -function desktop_mode_content_graph_flush_cache() {
456 +function openstation_content_graph_flush_cache() {
450 457 global $wpdb;
451 458 // Enumerate matching transient option names first, then route each
452 459 // through `delete_transient()` so WP's transient/options cache
453 460 // invalidation runs alongside the wp_options delete. The earlier
@@ -456,9 +463,9 @@
456 463 // the in-memory options cache and returned the stale payload.
457 464 // This bit the `set_object_terms` invalidation path because that
458 465 // hook doesn't update `post_modified_gmt`, so the cache key stayed
459 466 // identical and `get_transient` (cache hit) returned pre-retag data.
460 - $prefix_like = $wpdb->esc_like( '_transient_' . DESKTOP_MODE_CONTENT_GRAPH_TRANSIENT_PREFIX ) . '%';
467 + $prefix_like = $wpdb->esc_like( '_transient_' . OPENSTATION_CONTENT_GRAPH_TRANSIENT_PREFIX ) . '%';
461 468 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
462 469 $option_names = $wpdb->get_col(
463 470 $wpdb->prepare(
464 471 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
@@ -476,14 +483,14 @@
476 483 delete_transient( $transient );
477 484 }
478 485 }
479 486 }
480 -add_action( 'save_post', 'desktop_mode_content_graph_flush_cache' );
481 -add_action( 'deleted_post', 'desktop_mode_content_graph_flush_cache' );
487 +add_action( 'save_post', 'openstation_content_graph_flush_cache' );
488 +add_action( 'deleted_post', 'openstation_content_graph_flush_cache' );
482 489 // Term assignments can change outside the post-edit path (CLI, bulk
483 490 // quick-edit, REST). Without this the per-node `category_ids` / `tag_ids`
484 491 // the group-by UI reads would go stale until the 6h TTL expires.
485 -add_action( 'set_object_terms', 'desktop_mode_content_graph_flush_cache' );
492 +add_action( 'set_object_terms', 'openstation_content_graph_flush_cache' );
486 493
487 494 /**
488 495 * Bulk-fetch every (post_id → taxonomy → term_ids[]) mapping for the
489 496 * `category` and `post_tag` taxonomies in a single query. Used to
@@ -493,9 +500,9 @@
493 500 * @param int[] $post_ids
494 501 * @return array<int, array<string, int[]>> Outer key = post id; inner
495 502 * key = taxonomy slug; value = term ids the post is in.
496 503 */
497 -function desktop_mode_content_graph_collect_post_terms( array $post_ids ) {
504 +function openstation_content_graph_collect_post_terms( array $post_ids ) {
498 505 $post_ids = array_values( array_filter( array_map( 'intval', $post_ids ) ) );
499 506 if ( empty( $post_ids ) ) {
500 507 return array();
501 508 }
@@ -534,9 +541,9 @@
534 541 }
535 542
536 543 /**
537 544 * Bulk-fetch distinct revision authors per post for the requested
538 - * post ids. Used by `desktop_mode_content_graph_build()` to populate
545 + * post ids. Used by `openstation_content_graph_build()` to populate
539 546 * each node's `contributor_ids` array so the cluster-attractor force
540 547 * can pull collaborator posts toward both the primary author's
541 548 * cluster AND each contributor's cluster (weighted so the primary
542 549 * still wins).
@@ -547,9 +554,9 @@
547 554 *
548 555 * @param int[] $post_ids
549 556 * @return array<int, int[]> post_id => list of contributor user ids.
550 557 */
551 -function desktop_mode_content_graph_collect_post_contributors( array $post_ids ) {
558 +function openstation_content_graph_collect_post_contributors( array $post_ids ) {
552 559 $post_ids = array_values( array_filter( array_map( 'intval', $post_ids ) ) );
553 560 if ( empty( $post_ids ) ) {
554 561 return array();
555 562 }
@@ -589,9 +596,9 @@
589 596 *
590 597 * @param int[] $author_ids
591 598 * @return array<int, array{ name: string }>
592 599 */
593 -function desktop_mode_content_graph_format_author_catalog( array $author_ids ) {
600 +function openstation_content_graph_format_author_catalog( array $author_ids ) {
594 601 $author_ids = array_values( array_unique( array_filter( array_map( 'intval', $author_ids ) ) ) );
595 602 if ( empty( $author_ids ) ) {
596 603 return array();
597 604 }
@@ -601,9 +608,9 @@
601 608 'fields' => array( 'ID', 'display_name' ),
602 609 'number' => count( $author_ids ),
603 610 )
604 611 );
605 - $out = array();
612 + $out = array();
606 613 foreach ( (array) $query->get_results() as $user ) {
607 614 $out[ (int) $user->ID ] = array(
608 615 'name' => (string) $user->display_name,
609 616 );
@@ -619,9 +626,9 @@
619 626 * @param int[] $term_ids
620 627 * @param string $taxonomy
621 628 * @return array<int, array{ name: string }>
622 629 */
623 -function desktop_mode_content_graph_format_term_catalog( array $term_ids, $taxonomy ) {
630 +function openstation_content_graph_format_term_catalog( array $term_ids, $taxonomy ) {
624 631 $term_ids = array_values( array_unique( array_filter( array_map( 'intval', $term_ids ) ) ) );
625 632 if ( empty( $term_ids ) ) {
626 633 return array();
627 634 }
@@ -632,9 +639,9 @@
632 639 'hide_empty' => false,
633 640 'number' => count( $term_ids ),
634 641 )
635 642 );
636 - $out = array();
643 + $out = array();
637 644 if ( is_wp_error( $terms ) || ! is_array( $terms ) ) {
638 645 return $out;
639 646 }
640 647 foreach ( $terms as $term ) {