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/my-wordpress/user-list-fields.php +159 -31 0.9.61.1.10 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — My WordPress: per-user REST fields for the
3 + * OpenStation — My WordPress: per-user REST fields for the
4 4 * Users-folder list view.
5 5 *
6 6 * Surfaces a small "summary" payload on every `/wp/v2/users` row so
7 7 * the My WordPress folder window can paint a rich tile (avatar +
@@ -9,12 +9,12 @@
9 9 * N+1 dossier requests as the user scrolls.
10 10 *
11 11 * Fields:
12 12 *
13 - * - `desktop_mode_summary.postCount` int count of non-trash posts authored
14 - * - `desktop_mode_summary.roleLabels` array translated role labels (gated on `list_users` OR self)
15 - * - `desktop_mode_summary.registered` string ISO-8601 registered date (gated on `list_users` OR self)
16 - * - `desktop_mode_summary.lastActive` string ISO-8601 of latest published post, or '' when unknown
13 + * - `openstation_summary.postCount` int count of non-trash posts authored
14 + * - `openstation_summary.roleLabels` array translated role labels (gated on `list_users` OR self)
15 + * - `openstation_summary.registered` string ISO-8601 registered date (gated on `list_users` OR self)
16 + * - `openstation_summary.lastActive` string ISO-8601 of latest published post, or '' when unknown
17 17 *
18 18 * Each field is independently capability-gated so a viewer without
19 19 * `list_users` still sees `postCount` (public counts) and a public
20 20 * `lastActive` derived from publish dates, but doesn't see
@@ -19,25 +19,143 @@
19 19 * `list_users` still sees `postCount` (public counts) and a public
20 20 * `lastActive` derived from publish dates, but doesn't see
21 21 * `roleLabels` or `registered` (private).
22 22 *
23 - * @package WPDesktopMode
24 - * @since 0.8.2
23 + * @package OpenStation
25 24 */
26 25
27 26 defined( 'ABSPATH' ) || exit;
28 27
29 28 /**
30 - * Compute the summary payload for one user. Cheap enough to call
31 - * per-row (each branch is one indexed query); WP's object cache
32 - * absorbs repeats inside a single request.
29 + * The post types a summary counts. Kept in one place so the per-user
30 + * query and the grouped prefetch below can't drift apart — a tile
31 + * whose count changes depending on how the page was loaded is worse
32 + * than no count at all.
33 33 *
34 - * @since 0.8.2
34 + * @return string[]
35 + */
36 +function openstation_my_wordpress_user_summary_post_types() {
37 + return array( 'post', 'page' );
38 +}
39 +
40 +/**
41 + * Per-request store of prefetched post counts and last-active dates,
42 + * keyed by user id. Populated by
43 + * {@see openstation_my_wordpress_user_summary_prime()}; read by the
44 + * payload builder below.
35 45 *
46 + * @param array|null $write Rows to merge in, or null to read.
47 + * @return array<int,array{postCount:int,lastActive:string}>
48 + */
49 +function &openstation_my_wordpress_user_summary_cache( $write = null ) {
50 + static $cache = array();
51 +
52 + if ( is_array( $write ) ) {
53 + $cache = $cache + $write;
54 + }
55 +
56 + return $cache;
57 +}
58 +
59 +/**
60 + * Prefetch the two queried facts — authored-post count and latest
61 + * publish date — for a whole page of users in two grouped queries.
62 + *
63 + * Without this, a list view pays `count_user_posts()` plus a
64 + * `MAX(post_date_gmt)` lookup *per row*: 200 uncached queries for a
65 + * 100-row page. Call this once with the page's ids and the payload
66 + * builder answers from memory.
67 + *
68 + * @param int[] $user_ids User ids on the page.
69 + * @return void
70 + */
71 +function openstation_my_wordpress_user_summary_prime( $user_ids ) {
72 + global $wpdb;
73 +
74 + $cached = openstation_my_wordpress_user_summary_cache();
75 + $ids = array();
76 + foreach ( (array) $user_ids as $user_id ) {
77 + $user_id = (int) $user_id;
78 + if ( $user_id > 0 && ! isset( $cached[ $user_id ] ) ) {
79 + $ids[ $user_id ] = true;
80 + }
81 + }
82 +
83 + $ids = array_keys( $ids );
84 + if ( empty( $ids ) ) {
85 + return;
86 + }
87 +
88 + $types = openstation_my_wordpress_user_summary_post_types();
89 + $id_slots = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
90 + $type_slots = implode( ',', array_fill( 0, count( $types ), '%s' ) );
91 +
92 + $rows = array();
93 + foreach ( $ids as $user_id ) {
94 + $rows[ $user_id ] = array(
95 + 'postCount' => 0,
96 + 'lastActive' => '',
97 + );
98 + }
99 +
100 + // The count `count_user_posts( $id, $types, true )` would produce,
101 + // for every id at once. Its `$public_only = true` resolves — via
102 + // `get_posts_by_author_sql()` — to exactly `post_status =
103 + // 'publish'`, so that is what this reproduces.
104 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholder lists are built from counts, values are passed through prepare().
105 + $counts = $wpdb->get_results(
106 + $wpdb->prepare(
107 + "SELECT post_author, COUNT(*) AS total FROM {$wpdb->posts}
108 + WHERE post_author IN ( {$id_slots} )
109 + AND post_type IN ( {$type_slots} )
110 + AND post_status = 'publish'
111 + GROUP BY post_author",
112 + array_merge( $ids, $types )
113 + )
114 + );
115 +
116 + $actives = $wpdb->get_results(
117 + $wpdb->prepare(
118 + "SELECT post_author, MAX(post_date_gmt) AS latest FROM {$wpdb->posts}
119 + WHERE post_author IN ( {$id_slots} )
120 + AND post_status = 'publish'
121 + AND post_type IN ( {$type_slots} )
122 + GROUP BY post_author",
123 + array_merge( $ids, $types )
124 + )
125 + );
126 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
127 +
128 + foreach ( (array) $counts as $row ) {
129 + $user_id = (int) $row->post_author;
130 + if ( isset( $rows[ $user_id ] ) ) {
131 + $rows[ $user_id ]['postCount'] = (int) $row->total;
132 + }
133 + }
134 +
135 + foreach ( (array) $actives as $row ) {
136 + $user_id = (int) $row->post_author;
137 + if ( isset( $rows[ $user_id ] ) && $row->latest ) {
138 + $rows[ $user_id ]['lastActive'] = (string) mysql2date( 'c', $row->latest, false );
139 + }
140 + }
141 +
142 + openstation_my_wordpress_user_summary_cache( $rows );
143 +}
144 +
145 +/**
146 + * Compute the summary payload for one user.
147 + *
148 + * Answers from the prefetch when
149 + * {@see openstation_my_wordpress_user_summary_prime()} has run for
150 + * this id, and falls back to the two per-user queries when it hasn't —
151 + * so a single-row caller stays correct without having to know about
152 + * the batch path.
153 + *
36 154 * @param int $user_id User id.
37 155 * @return array{postCount:int,roleLabels:array<int,string>,registered:string,lastActive:string}
38 156 */
39 -function desktop_mode_my_wordpress_user_summary_payload( $user_id ) {
157 +function openstation_my_wordpress_user_summary_payload( $user_id ) {
40 158 $user_id = (int) $user_id;
41 159 if ( $user_id <= 0 ) {
42 160 return array(
43 161 'postCount' => 0,
@@ -59,12 +177,17 @@
59 177
60 178 $can_see_private = current_user_can( 'list_users' )
61 179 || ( get_current_user_id() === $user_id );
62 180
181 + $primed = openstation_my_wordpress_user_summary_cache();
182 + $types = openstation_my_wordpress_user_summary_post_types();
183 +
63 184 // `count_user_posts` accepts an array of post types since 4.5; we
64 185 // limit to public ones the dossier already counts so the tile and
65 186 // the dossier agree.
66 - $post_count = (int) count_user_posts( $user_id, array( 'post', 'page' ), true );
187 + $post_count = isset( $primed[ $user_id ] )
188 + ? (int) $primed[ $user_id ]['postCount']
189 + : (int) count_user_posts( $user_id, $types, true );
67 190
68 191 $role_labels = array();
69 192 if ( $can_see_private && function_exists( 'wp_roles' ) ) {
70 193 $wp_roles = wp_roles();
@@ -82,20 +205,27 @@
82 205
83 206 // Latest published post — useful at-a-glance "active recently?"
84 207 // signal for the tile. Drawn from `post_date_gmt` so timezone
85 208 // drift can't shift the displayed date.
86 - global $wpdb;
87 - $last_active_raw = $wpdb->get_var(
88 - $wpdb->prepare(
89 - "SELECT MAX(post_date_gmt) FROM {$wpdb->posts}
90 - WHERE post_author = %d
91 - AND post_status = 'publish'
92 - AND post_type IN ( 'post', 'page' )",
93 - $user_id
94 - )
95 - );
96 - $last_active = $last_active_raw ? mysql2date( 'c', $last_active_raw, false ) : '';
209 + if ( isset( $primed[ $user_id ] ) ) {
210 + $last_active = (string) $primed[ $user_id ]['lastActive'];
211 + } else {
212 + global $wpdb;
97 213
214 + $type_slots = implode( ',', array_fill( 0, count( $types ), '%s' ) );
215 + $last_active_raw = $wpdb->get_var(
216 + $wpdb->prepare(
217 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholder list is built from a count, values pass through prepare().
218 + "SELECT MAX(post_date_gmt) FROM {$wpdb->posts}
219 + WHERE post_author = %d
220 + AND post_status = 'publish'
221 + AND post_type IN ( {$type_slots} )",
222 + array_merge( array( $user_id ), $types )
223 + )
224 + );
225 + $last_active = $last_active_raw ? mysql2date( 'c', $last_active_raw, false ) : '';
226 + }
227 +
98 228 return array(
99 229 'postCount' => $post_count,
100 230 'roleLabels' => $role_labels,
101 231 'registered' => (string) $registered,
@@ -106,22 +236,20 @@
106 236 /**
107 237 * Register the field on the `user` REST resource. The field is
108 238 * read-only (no `update_callback`); `get_callback` receives the
109 239 * user response array, from which we read `id`.
110 - *
111 - * @since 0.8.2
112 240 */
113 -function desktop_mode_my_wordpress_register_user_summary_field() {
241 +function openstation_my_wordpress_register_user_summary_field() {
114 242 register_rest_field(
115 243 'user',
116 - 'desktop_mode_summary',
244 + 'openstation_summary',
117 245 array(
118 246 'get_callback' => static function ( $user ) {
119 247 $id = isset( $user['id'] ) ? (int) $user['id'] : 0;
120 - return desktop_mode_my_wordpress_user_summary_payload( $id );
248 + return openstation_my_wordpress_user_summary_payload( $id );
121 249 },
122 250 'schema' => array(
123 - 'description' => __( 'Compact user summary for My WordPress.', 'desktop-mode' ),
251 + 'description' => __( 'Compact user summary for the WP Explorer window.', 'desktop-mode' ),
124 252 'type' => 'object',
125 253 'context' => array( 'view', 'edit', 'embed' ),
126 254 'readonly' => true,
127 255 'properties' => array(
@@ -146,5 +274,5 @@
146 274 ),
147 275 )
148 276 );
149 277 }
150 -add_action( 'rest_api_init', 'desktop_mode_my_wordpress_register_user_summary_field' );
278 +add_action( 'rest_api_init', 'openstation_my_wordpress_register_user_summary_field' );