Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API: new endpoint to fetch post counts by post status #66294

Closed
wants to merge 12 commits into from
3 changes: 3 additions & 0 deletions backport-changelog/6.8/7773.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/7773

* https://github.com/WordPress/gutenberg/pull/66294
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<?php
/**
* REST API: Gutenberg_REST_Post_Counts_Controller class
*
* @since 6.8.0
*
* @package gutenberg
* @subpackage REST_API
*/

/**
* Core class used to return post counts by post type via the REST API.
*
* @since 6.8.0
*
* @see WP_REST_Controller
*/
class Gutenberg_REST_Post_Counts_Controller extends WP_REST_Controller {

/**
* Constructor.
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'counts';
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, I'm not sure that I love an endpoint specific for post counting. I am not against potentially adding this capability, but I think it feels more natural to be available under the wp/v2/<post_type>/count endpoint.

I am not sure if there's a technical reason why that would be difficult. But wondering if that could support retrieving the overall count for a specific post type (no arguments) OR with any other passed specific query arguments since they should already be supported within that post type's controller.

@TimothyBJacobs Do you have any opinion on any of these approaches?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @desrosj This is exactly the feedback I was after 🙇🏻

@Mamaduka also hinted in that direction above

I am not sure if there's a technical reason why that would be difficult

In principle, I don't see any barriers. In fact, that's what I started with locally at least.

I arrived here because the templates controller doesn't inherit the posts controller, and I didn't want to duplicate things, but rather, make the response embeddable.

However, if the consensus is that it'd be better for users to have a route on post-type endpoints, e.g., wp/v2/<post_type>/count, then I'm happy to switch things around.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've thrown up an alternative that adds a /count route to WP_REST_Posts_Controller, so wp/v2/<post_type>/count as @desrosj mentions above.

I'm feeling more comfortable with that version 😄

For Gutenberg, it's only needed for the page post type, but in the Core sync it'd be available for all post types that inherit from the controller.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm feeling more comfortable with that version 😄

I withdraw that position for the reasons stated in:

#66294 (comment)

}

/**
* Registers the routes for post counts.
*
* @since 6.8.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<post_type>[\w-]+)',
array(
'args' => array(
'post_type' => array(
'description' => __( 'An alphanumeric identifier for the post type.' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}

/**
* Checks if a given request has access to read post counts.
*
* @since 6.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
$post_type = get_post_type_object( $request['post_type'] );

if ( empty( $post_type ) ) {
return new WP_Error(
'rest_invalid_post_type',
__( 'Invalid post type.' ),
array( 'status' => 404 )
);
}

if ( ! current_user_can( $post_type->cap->read ) ) {
return new WP_Error(
'rest_cannot_read',
__( 'Sorry, you are not allowed to read post counts for this post type.' ),
array( 'status' => rest_authorization_required_code() )
);
}

return true;
}

/**
* Retrieves post counts for a specific post type.
*
* @since 6.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$post_type = $request['post_type'];
$counts = wp_count_posts( $post_type );
$data = $this->prepare_item_for_response( $counts, $request );

return rest_ensure_response( $data );
}

/**
* Prepares post counts for response.
*
* @since 6.8.0
*
* @param object $item Post counts data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
$data = array();

if ( ! empty( $item ) ) {
/*
* The fields comprise all non-internal post statuses,
* including any custom statuses that may be registered.
* 'trash' is an exception, so if it exists, it is added separately.
*/
$post_stati = get_post_stati( array( 'internal' => false ) );

if ( get_post_status_object( 'trash' ) ) {
$post_stati[] = 'trash';
}
// Include all public statuses in the response if there is a count.
foreach ( $post_stati as $status ) {
if ( isset( $item->$status ) ) {
$data[ $status ] = (int) $item->$status;
}
}
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );

return rest_ensure_response( $data );
}

/**
* Retrieves the post counts schema, conforming to JSON Schema.
*
* @since 6.8.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}

/*
* The fields comprise all non-internal post stati,
* including any custom statuses that may be registered.
* 'trash' is an exception, so if it exists, it is added separately.
* The caveat is that all custom post statuses
* must be registered at the highest priority, otherwise
* the endpoint will not return them.
*/
$post_statuses = get_post_stati( array( 'internal' => false ) );

if ( get_post_status_object( 'trash' ) ) {
$post_statuses[] = 'trash';
}
$schema_properties = array();
foreach ( $post_statuses as $post_status ) {
$schema_properties[ $post_status ] = array(
// translators: %s: Post status.
'description' => sprintf( __( 'The number of posts with the status %s.' ), $post_status ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
);
}

$schema = array(
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'post-counts',
'type' => 'object',
'properties' => $schema_properties,
);

$this->schema = $schema;

return $this->add_additional_fields_schema( $this->schema );
}
}
10 changes: 10 additions & 0 deletions lib/compat/wordpress-6.8/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,13 @@ function gutenberg_add_default_template_types_to_index( WP_REST_Response $respon
}

add_filter( 'rest_index', 'gutenberg_add_default_template_types_to_index' );

/**
* Registers the Post Counts REST API routes.
*/
function gutenberg_register_post_counts_routes() {
$post_counts_controller = new Gutenberg_REST_Post_Counts_Controller();
$post_counts_controller->register_routes();
}

add_action( 'rest_api_init', 'gutenberg_register_post_counts_routes' );
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php';
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-post-types-controller-6-8.php';
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-hierarchical-sort.php';
require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-post-counts-controller.php';
require __DIR__ . '/compat/wordpress-6.8/rest-api.php';

// Plugin specific code.
Expand Down
Loading
Loading