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/widgets/widget-post-stats.php +148 -30 0.9.61.1.10 View file →
@@ -1,81 +1,199 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Post Stats Widget.
3 + * OpenStation — Post Stats Widget.
4 4 *
5 5 * Bar chart of posts published per month for the last 6 months,
6 6 * broken down by published / draft / pending status.
7 7 *
8 8 * Refresh: every 5 minutes.
9 - * Requires: Desktop Mode 0.18.0+ (desktop_mode_register_widget).
9 + * Requires: OpenStation 0.18.0+ (openstation_register_widget).
10 10 *
11 - * @package WPDesktopMode
12 - * @since 0.26.0
11 + * @package OpenStation
13 12 */
14 13
15 14 defined( 'ABSPATH' ) || exit;
16 15
17 16 /**
17 + * Register the REST endpoint that aggregates per-month post counts
18 + * server-side. Replaces the widget's original client-side approach —
19 + * 3 statuses × up-to-100-per-page `/wp/v2/posts` requests every
20 + * refresh — with a single GROUP BY that's shared through a transient.
21 + *
22 + * Route: GET /desktop-mode/v1/post-stats
23 + * Permission: edit_posts.
24 + */
25 +function openstation_register_post_stats_rest_route() {
26 + register_rest_route(
27 + 'desktop-mode/v1',
28 + '/post-stats',
29 + array(
30 + 'methods' => WP_REST_Server::READABLE,
31 + 'callback' => 'openstation_post_stats_callback',
32 + 'permission_callback' => static function () {
33 + return current_user_can( 'edit_posts' );
34 + },
35 + )
36 + );
37 +}
38 +add_action( 'rest_api_init', 'openstation_register_post_stats_rest_route' );
39 +
40 +/**
41 + * Aggregate post counts per month × status for the last 6 months.
42 + *
43 + * Capability scoping mirrors what the widget's old `/wp/v2/posts`
44 + * queries returned: published posts count site-wide for anyone with
45 + * `edit_posts`, while draft / pending counts are scoped to the
46 + * current user's own posts unless they hold `edit_others_posts`
47 + * (core's REST posts controller applies the same visibility).
48 + *
49 + * Cached for 5 minutes per scope — the data is a trailing-6-month
50 + * aggregate, the widget refreshes every 5 minutes, and every viewer
51 + * in the same capability scope can share one computation.
52 + *
53 + * @return array{months:array<int,array{ym:string,publish:int,draft:int,pending:int}>}
54 + */
55 +function openstation_post_stats_callback() {
56 + global $wpdb;
57 +
58 + $see_others = current_user_can( 'edit_others_posts' );
59 + $cache_key = $see_others
60 + ? 'desktop_mode_post_stats_all'
61 + : 'desktop_mode_post_stats_own_' . get_current_user_id();
62 +
63 + $cached = get_transient( $cache_key );
64 + if ( is_array( $cached ) ) {
65 + return $cached;
66 + }
67 +
68 + $months_back = 6;
69 + // First day of the earliest bucket, site timezone.
70 + $cutoff = gmdate(
71 + 'Y-m-01 00:00:00',
72 + strtotime( current_time( 'Y-m-01' ) . ' -' . ( $months_back - 1 ) . ' months' )
73 + );
74 +
75 + // Two literal query branches (rather than a concatenated author
76 + // clause) so every byte of SQL inside prepare() is static —
77 + // keeps the PreparedSQL sniff able to verify it.
78 + if ( $see_others ) {
79 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- single aggregate GROUP BY, result cached in the transient below.
80 + $rows = $wpdb->get_results(
81 + $wpdb->prepare(
82 + "SELECT DATE_FORMAT( post_date, '%%Y-%%m' ) AS ym, post_status, COUNT(*) AS cnt
83 + FROM {$wpdb->posts}
84 + WHERE post_type = %s
85 + AND post_status IN ( 'publish', 'draft', 'pending' )
86 + AND post_date >= %s
87 + GROUP BY ym, post_status",
88 + 'post',
89 + $cutoff
90 + ),
91 + ARRAY_A
92 + );
93 + } else {
94 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- single aggregate GROUP BY, result cached in the transient below.
95 + $rows = $wpdb->get_results(
96 + $wpdb->prepare(
97 + "SELECT DATE_FORMAT( post_date, '%%Y-%%m' ) AS ym, post_status, COUNT(*) AS cnt
98 + FROM {$wpdb->posts}
99 + WHERE post_type = %s
100 + AND post_status IN ( 'publish', 'draft', 'pending' )
101 + AND post_date >= %s
102 + AND ( post_status = %s OR post_author = %d )
103 + GROUP BY ym, post_status",
104 + 'post',
105 + $cutoff,
106 + 'publish',
107 + get_current_user_id()
108 + ),
109 + ARRAY_A
110 + );
111 + }
112 +
113 + // Emit exactly $months_back buckets, oldest first, zero-filled —
114 + // the widget renders a fixed axis and shouldn't have to guess at
115 + // missing months.
116 + $buckets = array();
117 + for ( $i = $months_back - 1; $i >= 0; $i-- ) {
118 + $ym = gmdate( 'Y-m', strtotime( current_time( 'Y-m-01' ) . ' -' . $i . ' months' ) );
119 + $buckets[ $ym ] = array(
120 + 'ym' => $ym,
121 + 'publish' => 0,
122 + 'draft' => 0,
123 + 'pending' => 0,
124 + );
125 + }
126 + foreach ( (array) $rows as $row ) {
127 + $ym = isset( $row['ym'] ) ? (string) $row['ym'] : '';
128 + $status = isset( $row['post_status'] ) ? (string) $row['post_status'] : '';
129 + if ( isset( $buckets[ $ym ][ $status ] ) ) {
130 + $buckets[ $ym ][ $status ] = (int) $row['cnt'];
131 + }
132 + }
133 +
134 + $result = array( 'months' => array_values( $buckets ) );
135 +
136 + set_transient( $cache_key, $result, 5 * MINUTE_IN_SECONDS );
137 +
138 + return $result;
139 +}
140 +
141 +/**
18 142 * Register the JS + CSS assets.
19 - *
20 - * @since 0.26.0
21 143 */
22 -function desktop_mode_register_post_stats_widget_assets() {
23 - $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
24 - $version = defined( 'DESKTOP_MODE_VERSION' ) ? DESKTOP_MODE_VERSION : '0';
144 +function openstation_register_post_stats_widget_assets() {
145 + $suffix = openstation_asset_suffix();
146 + $version = defined( 'OPENSTATION_VERSION' ) ? OPENSTATION_VERSION : '0';
25 147
26 - $js_path = DESKTOP_MODE_DIR . 'assets/js/widget-post-stats' . $suffix . '.js';
27 - $css_path = DESKTOP_MODE_DIR . 'assets/js/widget-post-stats' . $suffix . '.css';
148 + $js_path = OPENSTATION_DIR . 'assets/js/widget-post-stats' . $suffix . '.js';
149 + $css_path = OPENSTATION_DIR . 'assets/js/widget-post-stats' . $suffix . '.css';
28 150
29 151 wp_register_style(
30 - 'desktop-mode-post-stats-widget',
31 - DESKTOP_MODE_URL . 'assets/js/widget-post-stats' . $suffix . '.css',
152 + 'os-post-stats-widget',
153 + OPENSTATION_URL . 'assets/js/widget-post-stats' . $suffix . '.css',
32 154 array(),
33 155 file_exists( $css_path ) ? (string) filemtime( $css_path ) : $version
34 156 );
35 157
36 158 wp_register_script(
37 - 'desktop-mode-post-stats-widget',
38 - DESKTOP_MODE_URL . 'assets/js/widget-post-stats' . $suffix . '.js',
159 + 'os-post-stats-widget',
160 + OPENSTATION_URL . 'assets/js/widget-post-stats' . $suffix . '.js',
39 161 array( 'wp-api-fetch' ),
40 162 file_exists( $js_path ) ? (string) filemtime( $js_path ) : $version,
41 163 true
42 164 );
43 165 }
44 -add_action( 'init', 'desktop_mode_register_post_stats_widget_assets', 5 );
166 +add_action( 'init', 'openstation_register_post_stats_widget_assets', 5 );
45 167
46 168 /**
47 169 * Eagerly enqueue the CSS on shell pages.
48 - *
49 - * @since 0.26.0
50 170 */
51 -function desktop_mode_enqueue_post_stats_widget_styles() {
52 - if ( function_exists( 'desktop_mode_is_enabled' ) && ! desktop_mode_is_enabled() ) {
171 +function openstation_enqueue_post_stats_widget_styles() {
172 + if ( function_exists( 'openstation_is_enabled' ) && ! openstation_is_enabled() ) {
53 173 return;
54 174 }
55 - if ( function_exists( 'desktop_mode_is_chromeless_request' ) && desktop_mode_is_chromeless_request() ) {
175 + if ( function_exists( 'openstation_is_chromeless_request' ) && openstation_is_chromeless_request() ) {
56 176 return;
57 177 }
58 - wp_enqueue_style( 'desktop-mode-post-stats-widget' );
178 + wp_enqueue_style( 'os-post-stats-widget' );
59 179 }
60 -add_action( 'admin_enqueue_scripts', 'desktop_mode_enqueue_post_stats_widget_styles', 20 );
180 +add_action( 'admin_enqueue_scripts', 'openstation_enqueue_post_stats_widget_styles', 20 );
61 181
62 182 /**
63 183 * Register the widget definition.
64 - *
65 - * @since 0.26.0
66 184 */
67 -function desktop_mode_register_post_stats_widget() {
68 - if ( ! function_exists( 'desktop_mode_register_widget' ) ) {
185 +function openstation_register_post_stats_widget() {
186 + if ( ! function_exists( 'openstation_register_widget' ) ) {
69 187 return;
70 188 }
71 - desktop_mode_register_widget(
189 + openstation_register_widget(
72 190 'desktop-mode/post-stats',
73 191 array(
74 192 'label' => __( 'Post Stats', 'desktop-mode' ),
75 193 'description' => __( 'Bar chart of posts per month over the last 6 months.', 'desktop-mode' ),
76 194 'icon' => 'dashicons-chart-bar',
77 - 'script' => 'desktop-mode-post-stats-widget',
195 + 'script' => 'os-post-stats-widget',
78 196 'movable' => true,
79 197 'resizable' => true,
80 198 'min_width' => 260,
81 199 'min_height' => 200,
@@ -83,5 +201,5 @@
83 201 'default_height' => 260,
84 202 )
85 203 );
86 204 }
87 -add_action( 'init', 'desktop_mode_register_post_stats_widget', 6 );
205 +add_action( 'init', 'openstation_register_post_stats_widget', 6 );