Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Stats: expose a single media item route on the stats-app proxy, so the Odyssey video details page can show the video's title, date, thumbnail, and duration.
62 changes: 62 additions & 0 deletions projects/packages/stats-admin/src/class-rest-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ public function register_rest_routes() {
)
);

// Single media item info (e.g. the video shown on the video details page).
register_rest_route(
static::$namespace,
sprintf( '/sites/%d/media/(?P<resource_id>[\d]+)', Jetpack_Options::get_option( 'id' ) ),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there any videopress endpoint we could reuse instead of creating our own.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good timing to reconsider this — since the PR was opened, the Calypso video details page stopped needing the media item entirely: the title/date now come from the stats/video/:id response's attachment post, and the thumbnail + retention cards were dropped (Automattic/wp-calypso#112499, #112528). So this route currently has no consumer and the PR is on hold.

The preferred path for bringing retention back is STATS-312 — extend stats/video/:id with range params and statType=all (ideally including the video duration in the response), which reuses the stats route this proxy already forwards. If that lands, I'd close this PR as unnecessary. And if a media route is ever needed after all, agreed we should first scope reusing core wp/v2/media/:id locally rather than adding a stats-app one.

array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_single_media_item' ),
'permission_callback' => array( $this, 'can_user_view_general_stats_callback' ),
)
);

// General stats for the site.
register_rest_route(
static::$namespace,
Expand Down Expand Up @@ -658,6 +669,57 @@ public function get_single_post( $req ) {
);
}

/**
* Get brief information for a single media item.
*
* @param WP_REST_Request $req The request object.
* @return array|WP_Error
*/
public function get_single_media_item( $req ) {
$media_id = intval( $req->get_param( 'resource_id' ) );
$post = get_post( $media_id, OBJECT, 'display' );
if ( empty( $post ) || 'attachment' !== $post->post_type ) {
return new WP_Error(
'unknown_media',
'Unknown media',
array( 'status' => 404 )
);
}

// The response should be as compatible as possible with `/sites/$site_id/media/$media_id`.
// Like `get_single_post`, the request is not forwarded to WordPress.com because that
// might require user tokens, which is not possible for users without a WordPress.com account.
$metadata = wp_get_attachment_metadata( $media_id );
$length = null;
$thumbnails = array();

if ( ! empty( $metadata['length'] ) ) {
$length = intval( $metadata['length'] );
} elseif ( ! empty( $metadata['videopress']['duration'] ) ) {
// VideoPress stores the duration in milliseconds.
$length = intval( round( $metadata['videopress']['duration'] / 1000 ) );
}

if ( ! empty( $metadata['videopress']['poster'] ) ) {
// VideoPress format thumbnails (fmt_*) are only generated on WordPress.com;
// map the poster to the largest format so consumers find a thumbnail where expected.
$thumbnails['fmt_hd'] = $metadata['videopress']['poster'];
} elseif ( get_the_post_thumbnail_url( $post->ID ) ) {
$thumbnails['fmt_std'] = get_the_post_thumbnail_url( $post->ID );
}

return array(
'ID' => $post->ID,
'site_ID' => Jetpack_Options::get_option( 'id' ),
'title' => $post->post_title,
'date' => $post->post_date,
'URL' => wp_get_attachment_url( $post->ID ),
'mime_type' => $post->post_mime_type,
'length' => $length,
'thumbnails' => (object) $thumbnails,
);
}

/**
* Get site stats.
*
Expand Down
35 changes: 35 additions & 0 deletions projects/packages/stats-admin/tests/php/REST_Controller_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,41 @@ public function assert_route_not_supported( $route ) {
$this->assertNotEquals( 200, $response->get_status() );
}

/**
* Test '/jetpack/v4/stats-app/sites/999/media/{media_id}'
*/
public function test_get_single_media_item() {
wp_set_current_user( $this->admin_id );

$attachment_id = wp_insert_attachment(
array(
'post_title' => 'Test video',
'post_mime_type' => 'video/mp4',
'post_status' => 'inherit',
)
);
wp_update_attachment_metadata( $attachment_id, array( 'length' => 95 ) );

$request = new WP_REST_Request(
'GET',
sprintf( '/jetpack/v4/stats-app/sites/999/media/%d', $attachment_id )
);
$request->set_header( 'content-type', 'application/json' );
$response = $this->server->dispatch( $request );

$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'Test video', $data['title'] );
$this->assertEquals( 95, $data['length'] );

// Unknown media items return 404.
$request = new WP_REST_Request( 'GET', '/jetpack/v4/stats-app/sites/999/media/987654' );
$request->set_header( 'content-type', 'application/json' );
$response = $this->server->dispatch( $request );

$this->assertEquals( 404, $response->get_status() );
}

/**
* Test '/jetpack/v4/stats-app/sites/999/site-has-never-published-post'
*/
Expand Down
Loading