Skip to content

Commit ac4bbd1

Browse files
BogdanUngureanumatticbot
authored andcommitted
Update the stats icon and add pageview count in the post list (#42218)
* Admin menu: use wpcom_get_custom_admin_menu_class instead of the class directly Instead of calling the admin menu class, we now use a helper function that returns the object. This also removes a quick fix that prevented the quick switcher to be registered twice in the UI. * remove the quick fix from dashboard_switcher_scripts * Stats: Update UI and move the code in the package * Stats: Update UI and move the code in the package * Fixed fatals * Fix linting * Add client support for simple sites * Fix linting * Try to fix the linting * Add wpcom to the stubs * Refactor WPCOM_Stats implementation and add unit testing * Refactor some code, add some changes in the phan directory and add more tests * Handle a scenario and fix failing tests. * use onlyMethods * Disable phan rule since we already check if it exists. * Fix linting * Create a single instance of the formatter * Update baseline * Add fallback to NumberFormatter * Fix phan linting. * echo the title * Remove target and update docblock * Revert "Update baseline" This reverts commit d724afee2167957c6f6e2cebd41029c3ba3ba19a. * update baseline * Update number of days to 30 * Update the link title * Update title that better reflects the information on the page and the destination page. Committed via a GitHub action: https://github.com/Automattic/jetpack/actions/runs/14174789903 Upstream-Ref: Automattic/jetpack@e9a49af
1 parent 64a7e24 commit ac4bbd1

File tree

8 files changed

+401
-73
lines changed

8 files changed

+401
-73
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
<?php
2+
/**
3+
* A class that adds a stats column to wp-admin Post List.
4+
*
5+
* @package automattic/jetpack-stats-admin
6+
*/
7+
8+
namespace Automattic\Jetpack\Stats_Admin;
9+
10+
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11+
use Automattic\Jetpack\Redirect;
12+
use Automattic\Jetpack\Stats\Options as Stats_Options;
13+
use Automattic\Jetpack\Stats\WPCOM_Stats;
14+
use Automattic\Jetpack\Status\Host;
15+
use NumberFormatter;
16+
17+
/**
18+
* Add a Stats column in the post and page lists.
19+
*/
20+
class Admin_Post_List_Column {
21+
22+
/**
23+
* Create the object.
24+
*
25+
* @return self
26+
*/
27+
public static function register() {
28+
return new self();
29+
}
30+
31+
/**
32+
* A list of NumberFormatters.
33+
*
34+
* @var \NumberFormatter[]
35+
*/
36+
private $formatter;
37+
38+
/**
39+
* The constructor.
40+
*/
41+
public function __construct() {
42+
// Add an icon to see stats in WordPress.com for a particular post.
43+
add_action( 'admin_print_styles-edit.php', array( $this, 'stats_load_admin_css' ) );
44+
45+
add_filter( 'manage_posts_columns', array( $this, 'add_stats_post_table' ) );
46+
add_filter( 'manage_pages_columns', array( $this, 'add_stats_post_table' ) );
47+
48+
add_action( 'manage_posts_custom_column', array( $this, 'add_stats_post_table_cell' ), 10, 2 );
49+
add_action( 'manage_pages_custom_column', array( $this, 'add_stats_post_table_cell' ), 10, 2 );
50+
}
51+
52+
/**
53+
* Load CSS needed for Stats column width in WP-Admin area.
54+
*
55+
* @since 4.7.0
56+
*/
57+
public function stats_load_admin_css() {
58+
?>
59+
<style type="text/css">
60+
.fixed .column-stats {
61+
width: 5em;
62+
}
63+
</style>
64+
<?php
65+
}
66+
67+
/**
68+
* Set content for cell with link to an entry's stats in Odyssey Stats.
69+
*
70+
* @param string $column The name of the column to display.
71+
* @param int $post_id The current post ID.
72+
*
73+
* @since 4.7.0
74+
*/
75+
public function add_stats_post_table_cell( $column, $post_id ) {
76+
if ( 'stats' === $column ) {
77+
if ( 'publish' !== get_post_status( $post_id ) ) {
78+
printf(
79+
'<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
80+
esc_html__( 'No stats', 'jetpack-stats-admin' )
81+
);
82+
} else {
83+
// Link to the wp-admin stats page.
84+
$stats_post_url = admin_url( sprintf( 'admin.php?page=stats#!/stats/post/%d/%d', $post_id, \Jetpack_Options::get_option( 'id', 0 ) ) );
85+
// Unless the user is on a Default style WOA site, in which case link to Calypso.
86+
if ( ( new Host() )->is_woa_site() && Stats_Options::get_option( 'enable_odyssey_stats' ) && 'wp-admin' !== get_option( 'wpcom_admin_interface' ) ) {
87+
$stats_post_url = Redirect::get_url(
88+
'calypso-stats-post',
89+
array(
90+
'path' => $post_id,
91+
)
92+
);
93+
}
94+
95+
static $post_views = null;
96+
97+
/**
98+
* Jetpack_stats_get_post_page_views_for_current_list makes a request with all post ids in the current $wp_query.
99+
* This way, we'll make a single API request instead of making one for each post.
100+
*
101+
* For this reason, we'll cache the result with the static $post_views variable.
102+
*/
103+
if ( null === $post_views ) {
104+
$post_views = $this->get_post_page_views_for_current_list();
105+
}
106+
107+
$views = $post_views[ $post_id ] ?? null;
108+
109+
$current_locale = get_bloginfo( 'language' );
110+
111+
$formatted_views = class_exists( '\NumberFormatter' ) ? $this->get_formatter( $current_locale )->format( $views ) : $this->get_fallback_format_to_compact_version( $views );
112+
113+
?>
114+
<a href="<?php echo esc_url( $stats_post_url ); ?>"
115+
title="<?php echo esc_html__( 'Views for the last thirty days. Click for detailed stats', 'jetpack-stats-admin' ); ?>">
116+
<span
117+
class="dashicons dashicons-visibility"></span>&nbsp;<span><?php echo null !== $views ? esc_html( $formatted_views ) : ''; ?></span>
118+
</a>
119+
<?php
120+
}
121+
}
122+
}
123+
124+
/**
125+
* Set header for column that allows to view an entry's stats.
126+
*
127+
* @param array $columns An array of column names.
128+
*
129+
* @return mixed
130+
*/
131+
public function add_stats_post_table( $columns ) {
132+
/*
133+
* Stats can be accessed in wp-admin or in Calypso,
134+
* depending on what version of the stats screen is enabled on your site.
135+
*
136+
* In both cases, the user must be allowed to access stats.
137+
*
138+
* If the Odyssey Stats experience isn't enabled, the user will need to go to Calypso,
139+
* so they need to be connected to WordPress.com to be able to access that page.
140+
*/
141+
if (
142+
! current_user_can( 'view_stats' )
143+
|| (
144+
! Stats_Options::get_option( 'enable_odyssey_stats' )
145+
&& ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected()
146+
)
147+
) {
148+
return $columns;
149+
}
150+
151+
// Array-Fu to add before comments.
152+
$pos = array_search( 'comments', array_keys( $columns ), true );
153+
154+
// Fallback to the last position if the post type does not support comments.
155+
if ( ! is_int( $pos ) ) {
156+
$pos = count( $columns );
157+
}
158+
159+
$chunks = array_chunk( $columns, $pos, true );
160+
$chunks[0]['stats'] = esc_html__( 'Stats', 'jetpack-stats-admin' );
161+
162+
return call_user_func_array( 'array_merge', $chunks );
163+
}
164+
165+
/**
166+
* Get a list of post views for each post id from the global $wp_query.
167+
*
168+
* @return array
169+
*/
170+
public function get_post_page_views_for_current_list(): array {
171+
global $wp_query;
172+
173+
if ( ! $wp_query->posts ) {
174+
return array();
175+
}
176+
177+
$post_ids = wp_list_pluck( $wp_query->posts, 'ID' );
178+
179+
$wpcom_stats = $this->get_stats();
180+
$post_views = $wpcom_stats->get_total_post_views(
181+
array(
182+
'num' => 30,
183+
'post_ids' => implode( ',', $post_ids ),
184+
)
185+
);
186+
187+
if ( is_wp_error( $post_views ) || empty( $post_views ) ) {
188+
return array();
189+
}
190+
191+
$views = array();
192+
193+
foreach ( $post_views['posts'] as $post ) {
194+
$views[ $post['ID'] ] = $post['views'];
195+
}
196+
197+
return $views;
198+
}
199+
200+
/**
201+
* Get the stats object.
202+
*
203+
* @return WPCOM_Stats
204+
*/
205+
protected function get_stats() {
206+
return new WPCOM_Stats();
207+
}
208+
209+
/**
210+
* Get the NumberFormatter instance.
211+
*
212+
* @param string $locale The current locale.
213+
*
214+
* @return NumberFormatter
215+
*/
216+
protected function get_formatter( string $locale ): \NumberFormatter {
217+
if ( isset( $this->formatter[ $locale ] ) ) {
218+
return $this->formatter[ $locale ];
219+
}
220+
221+
/**
222+
* PHP's NumberFormatter is just a wrapper over the ICU C library. The library does support decimal compact short formatter, but PHP doesn't have a stub for it (=< PHP 8.4).
223+
*
224+
* @see https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/unum_8h.html UNUM_DECIMAL_COMPACT_SHORT constant.
225+
*/
226+
$compact_decimal_short = 14;
227+
228+
/**
229+
* NumberFormatter::DECIMAL_COMPACT_SHORT only exists in PHP 8.5 and later. At this time, NumberFormatter::DECIMAL_COMPACT_SHORT only exists in PHP `main` branch.
230+
*
231+
* Use the constant if it's defined since it's safer.
232+
*/
233+
if ( defined( '\NumberFormatter::DECIMAL_COMPACT_SHORT' ) ) {
234+
// @phan-suppress-next-line PhanUndeclaredConstantOfClass
235+
$compact_decimal_short = NumberFormatter::DECIMAL_COMPACT_SHORT;
236+
}
237+
238+
try {
239+
$formatter = new \NumberFormatter( $locale, $compact_decimal_short );
240+
$formatter->setAttribute( \NumberFormatter::MAX_FRACTION_DIGITS, 1 );
241+
} catch ( \Exception $e ) {
242+
// Fallback to decimal if for some reason it fails to work.
243+
$formatter = new \NumberFormatter( $locale, \NumberFormatter::DECIMAL );
244+
}
245+
246+
$this->formatter[ $locale ] = $formatter;
247+
248+
return $formatter;
249+
}
250+
251+
/**
252+
* Fallback Format a number to a compact version if the Intl extension is not available.
253+
*
254+
* @param int $views The given number.
255+
*
256+
* @return string
257+
*/
258+
public function get_fallback_format_to_compact_version( $views ) {
259+
if ( $views >= 10000000 ) {
260+
return round( $views / 1000000 ) . 'M';
261+
} elseif ( $views >= 1000000 ) {
262+
$views = round( $views / 1000000, 1 );
263+
return preg_replace( '/\.0$/', '', (string) $views ) . 'M';
264+
} elseif ( $views >= 10000 ) {
265+
return round( $views / 1000 ) . 'K';
266+
} elseif ( $views >= 1000 ) {
267+
$views = round( $views / 1000, 1 );
268+
return preg_replace( '/\.0$/', '', (string) $views ) . 'K';
269+
}
270+
271+
return (string) $views;
272+
}
273+
}

Diff for: jetpack_vendor/automattic/jetpack-stats-admin/src/class-main.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Main {
2222
/**
2323
* Stats version.
2424
*/
25-
const VERSION = '0.24.7-alpha';
25+
const VERSION = '0.25.0-alpha';
2626

2727
/**
2828
* Singleton Main instance.

Diff for: jetpack_vendor/automattic/jetpack-stats/src/class-wpcom-stats.php

+53
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Automattic\Jetpack\Stats;
99

1010
use Automattic\Jetpack\Connection\Client;
11+
use Automattic\Jetpack\Status\Host;
1112
use Jetpack_Options;
1213
use WP_Error;
1314

@@ -49,6 +50,20 @@ class WPCOM_Stats {
4950
*/
5051
protected $resource;
5152

53+
/**
54+
* If the site is on WPCOM Simple.
55+
*
56+
* @var bool
57+
*/
58+
protected $is_wpcom_simple;
59+
60+
/**
61+
* The constructor.
62+
*/
63+
public function __construct() {
64+
$this->is_wpcom_simple = ( new Host() )->is_wpcom_simple();
65+
}
66+
5267
/**
5368
* Get site's stats.
5469
*
@@ -309,6 +324,31 @@ public function get_search_terms( $args = array() ) {
309324
* @return array|WP_Error
310325
*/
311326
public function get_total_post_views( $args = array() ) {
327+
if ( $this->is_wpcom_simple ) {
328+
$post_ids = isset( $args['post_ids'] ) ? explode( ',', $args['post_ids'] ) : array();
329+
$escaped_post_ids = implode( ',', array_map( 'esc_sql', $post_ids ) );
330+
331+
$number_of_days = isset( $args['num'] ) ? absint( $args['num'] ) : 1;
332+
// It's the same function used in WPCOM simple.
333+
// @phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
334+
$end_date = $args['end'] ?? date( 'Y-m-d' );
335+
336+
$stats = $this->fetch_stats_on_wpcom_simple( $end_date, $number_of_days, $escaped_post_ids );
337+
338+
$post_views = $stats['-'] ?? array();
339+
340+
$posts = array_map(
341+
function ( $post_id ) use ( $post_views ) {
342+
return array(
343+
'ID' => $post_id,
344+
'views' => $post_views[ $post_id ] ?? 0,
345+
);
346+
},
347+
$post_ids
348+
);
349+
350+
return array( 'posts' => $posts );
351+
}
312352

313353
$this->resource = 'views/posts';
314354

@@ -506,6 +546,19 @@ protected function fetch_remote_stats( $endpoint, $args ) {
506546
return json_decode( $response_body, true );
507547
}
508548

549+
/**
550+
* Fetch the stats when executed in WPCOM Simple.
551+
*
552+
* @param string $end_date The end date.
553+
* @param int $number_of_days The number of days.
554+
* @param string $escaped_post_ids The escaped post ids.
555+
*
556+
* @return array
557+
*/
558+
protected function fetch_stats_on_wpcom_simple( $end_date, $number_of_days, $escaped_post_ids ) {
559+
return stats_get_daily_history( null, get_current_blog_id(), 'postviews', 'post_id', $end_date, $number_of_days, " AND post_id IN ($escaped_post_ids)", 0, true );
560+
}
561+
509562
/**
510563
* Convert stats array to object after sanity checking the array is valid.
511564
*

Diff for: jetpack_vendor/i18n-map.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@
7070
),
7171
'jetpack-stats' => array(
7272
'path' => 'jetpack_vendor/automattic/jetpack-stats',
73-
'ver' => '0.15.9-alpha1742998768',
73+
'ver' => '0.15.9-alpha1743433444',
7474
),
7575
'jetpack-stats-admin' => array(
7676
'path' => 'jetpack_vendor/automattic/jetpack-stats-admin',
77-
'ver' => '0.24.7-alpha1742998768',
77+
'ver' => '0.25.0-alpha1743433444',
7878
),
7979
'jetpack-subscribers-dashboard' => array(
8080
'path' => 'jetpack_vendor/automattic/jetpack-subscribers-dashboard',

Diff for: vendor/composer/autoload_classmap.php

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
'Automattic\\Jetpack\\Stats\\Tracking_Pixel' => $baseDir . '/jetpack_vendor/automattic/jetpack-stats/src/class-tracking-pixel.php',
165165
'Automattic\\Jetpack\\Stats\\WPCOM_Stats' => $baseDir . '/jetpack_vendor/automattic/jetpack-stats/src/class-wpcom-stats.php',
166166
'Automattic\\Jetpack\\Stats\\XMLRPC_Provider' => $baseDir . '/jetpack_vendor/automattic/jetpack-stats/src/class-xmlrpc-provider.php',
167+
'Automattic\\Jetpack\\Stats_Admin\\Admin_Post_List_Column' => $baseDir . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-admin-post-list-column.php',
167168
'Automattic\\Jetpack\\Stats_Admin\\Dashboard' => $baseDir . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-dashboard.php',
168169
'Automattic\\Jetpack\\Stats_Admin\\Main' => $baseDir . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-main.php',
169170
'Automattic\\Jetpack\\Stats_Admin\\Notices' => $baseDir . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-notices.php',

Diff for: vendor/composer/autoload_static.php

+1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ class ComposerStaticInit26841ac2064774301cbe06d174833bfc_wpcomshⓥ6_1_0_alpha
193193
'Automattic\\Jetpack\\Stats\\Tracking_Pixel' => __DIR__ . '/../..' . '/jetpack_vendor/automattic/jetpack-stats/src/class-tracking-pixel.php',
194194
'Automattic\\Jetpack\\Stats\\WPCOM_Stats' => __DIR__ . '/../..' . '/jetpack_vendor/automattic/jetpack-stats/src/class-wpcom-stats.php',
195195
'Automattic\\Jetpack\\Stats\\XMLRPC_Provider' => __DIR__ . '/../..' . '/jetpack_vendor/automattic/jetpack-stats/src/class-xmlrpc-provider.php',
196+
'Automattic\\Jetpack\\Stats_Admin\\Admin_Post_List_Column' => __DIR__ . '/../..' . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-admin-post-list-column.php',
196197
'Automattic\\Jetpack\\Stats_Admin\\Dashboard' => __DIR__ . '/../..' . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-dashboard.php',
197198
'Automattic\\Jetpack\\Stats_Admin\\Main' => __DIR__ . '/../..' . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-main.php',
198199
'Automattic\\Jetpack\\Stats_Admin\\Notices' => __DIR__ . '/../..' . '/jetpack_vendor/automattic/jetpack-stats-admin/src/class-notices.php',

0 commit comments

Comments
 (0)