Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 91 additions & 31 deletions wp-content/plugins/wporg-learn/inc/activity-kit-rest.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ function handle_stats( $request ) {
add_filter( 'jetpack_fetch_stats_cache_expiration', __NAMESPACE__ . '\stats_cache_expiration' );

if ( 'both' === $metric || 'views' === $metric ) {
$views_map = get_jetpack_post_views( $range );
$kit_ids = wp_list_pluck( $kits, 'ID' );
$views_map = get_jetpack_post_views( $range, $kit_ids );
}
if ( 'both' === $metric || 'downloads' === $metric ) {
$downloads_map = get_jetpack_download_clicks( $range, $zip_url_map );
Expand Down Expand Up @@ -155,47 +156,106 @@ function handle_stats( $request ) {
/**
* Get per-post view counts from Jetpack Stats for a given time range.
*
* @param string $range One of '7d', '30d', '90d', 'all'.
* @return array Map of post_id (int) => view_count (int). Empty on failure.
* Uses get_total_post_views() rather than get_top_posts() so that views are
* fetched by post ID directly. get_top_posts() only returns the site-wide top N
* posts ranked by all-time views, which means newly published activity kits
* never appear — they're outranked by years of established content.
*
* API constraints (Jetpack / WPCOM /stats/views/posts endpoint):
* - `period` is silently discarded; only daily granularity is returned.
* - `num` is capped at 30 days per call (422 if larger).
* - `post_ids` accepts at most 100 IDs per call.
*
* To cover ranges longer than 30 days, multiple 30-day windows are issued with
* a date offset and the results are summed. Kit IDs are chunked into groups of
* 100 so the library can grow past 100 kits without silently losing data.
*
* @param string $range One of '7d', '30d', '90d', 'all'.
* @param int[] $kit_ids Post IDs of the activity kits to fetch views for.
* @return array Map of post_id (int) => view_count (int). Empty on failure.
*/
function get_jetpack_post_views( $range ) {
if ( ! class_exists( '\Automattic\Jetpack\Stats\WPCOM_Stats' ) ) {
function get_jetpack_post_views( $range, array $kit_ids ) {
if ( ! class_exists( '\Automattic\Jetpack\Stats\WPCOM_Stats' ) || empty( $kit_ids ) ) {
return array();
}

$stats = new \Automattic\Jetpack\Stats\WPCOM_Stats();

if ( 'all' === $range ) {
$period = 'month';
$num = 36;
} else {
$period = 'day';
$num = intval( str_replace( 'd', '', $range ) );
/*
* Map the UI range to one or more 30-day windows. Each window is defined by
* how many days back its end-date is offset from today. The 'all' range is
* capped at 90 days (3 × 30) — covering more would require an unreasonable
* number of sequential API calls.
*/
switch ( $range ) {
case '7d':
$windows = array(
array(
'num' => 7,
'offset' => 0,
),
);
break;
case '30d':
$windows = array(
array(
'num' => 30,
'offset' => 0,
),
);
break;
case '90d':
case 'all':
default:
$windows = array(
array(
'num' => 30,
'offset' => 0,
),
array(
'num' => 30,
'offset' => 30,
),
array(
'num' => 30,
'offset' => 60,
),
);
break;
Comment thread
obenland marked this conversation as resolved.
}

$result = $stats->get_top_posts(
array(
'period' => $period,
'num' => $num,
'date' => gmdate( 'Y-m-d' ),
'summarize' => true,
'max' => 1000,
)
);
$chunks = array_chunk( $kit_ids, 100 );
$map = array();

if ( is_wp_error( $result ) || ! is_array( $result ) ) {
return array();
}
foreach ( $chunks as $chunk ) {
$post_ids_str = implode( ',', array_map( 'absint', $chunk ) );

$post_views = isset( $result['summary']['postviews'] ) ? $result['summary']['postviews'] : array();
if ( ! is_array( $post_views ) ) {
return array();
}
foreach ( $windows as $window ) {
$date = gmdate( 'Y-m-d', time() - $window['offset'] * DAY_IN_SECONDS );
$result = $stats->get_total_post_views(
array(
'post_ids' => $post_ids_str,
'num' => $window['num'],
'date' => $date,
)
);

$map = array();
foreach ( $post_views as $post_data ) {
if ( isset( $post_data['id'], $post_data['views'] ) ) {
$map[ (int) $post_data['id'] ] = (int) $post_data['views'];
if ( is_wp_error( $result ) || ! is_array( $result ) ) {
continue;
}

$post_views = isset( $result['posts'] ) ? $result['posts'] : array();
if ( ! is_array( $post_views ) ) {
continue;
}

foreach ( $post_views as $post_data ) {
// The views/posts API uses uppercase 'ID' (unlike top-posts which uses 'id').
if ( isset( $post_data['ID'], $post_data['views'] ) ) {
$id = (int) $post_data['ID'];
$map[ $id ] = ( isset( $map[ $id ] ) ? $map[ $id ] : 0 ) + (int) $post_data['views'];
}
}
}
}

Expand Down
Loading